/
dbfixtures.go
187 lines (163 loc) 路 5.47 KB
/
dbfixtures.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package dbfixtures
import (
"context"
"database/sql"
"fmt"
"os"
"path"
fixtures "github.com/go-testfixtures/testfixtures/v3"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
migrate "github.com/rubenv/sql-migrate"
// SQL drivers.
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
"github.com/ovh/venom"
)
// Name of the executor.
const Name = "dbfixtures"
// New returns a new executor that can load
// database fixtures.
func New() venom.Executor {
return &Executor{}
}
// Executor is a venom executor that can load
// fixtures in many databases, using YAML schemas.
type Executor struct {
Files []string `json:"files" yaml:"files"`
Folder string `json:"folder" yaml:"folder"`
Database string `json:"database" yaml:"database"`
DSN string `json:"dsn" yaml:"dsn"`
Schemas []string `json:"schemas" yaml:"schemas"`
Migrations string `json:"migrations" yaml:"migrations"`
MigrationsTable string `json:"migrationsTable" yaml:"migrationsTable"`
SkipResetSequences bool `json:"skipResetSequences" yaml:"skipResetSequences"`
}
// Result represents a step result.
type Result struct {
Executor Executor `json:"executor,omitempty" yaml:"executor,omitempty"`
}
// Run implements the venom.Executor interface for Executor.
func (e Executor) Run(ctx context.Context, step venom.TestStep) (interface{}, error) {
// Transform step to Executor instance.
if err := mapstructure.Decode(step, &e); err != nil {
return nil, err
}
// Connect to the database and ping it.
venom.Debug(ctx, "connecting to database %s, %s\n", e.Database, e.DSN)
db, err := sql.Open(e.Database, e.DSN)
if err != nil {
return nil, errors.Wrapf(err, "failed to connect to database")
}
defer db.Close()
if err = db.Ping(); err != nil {
return nil, errors.Wrapf(err, "failed to ping database")
}
workdir := venom.StringVarFromCtx(ctx, "venom.testsuite.workdir")
// Load and import the schemas in the database
// if the argument is specified.
if len(e.Schemas) != 0 {
for _, s := range e.Schemas {
venom.Debug(ctx, "loading schema from file %s\n", s)
s = path.Join(workdir, s)
sbytes, errs := os.ReadFile(s)
if errs != nil {
return nil, errs
}
if _, err = db.Exec(string(sbytes)); err != nil {
return nil, errors.Wrapf(err, "failed to exec schema from file %q", s)
}
}
} else if e.Migrations != "" {
venom.Debug(ctx, "loading migrations from folder %s\n", e.Migrations)
if e.MigrationsTable != "" {
migrate.SetTable(e.MigrationsTable)
}
dir := path.Join(workdir, e.Migrations)
migrations := &migrate.FileMigrationSource{
Dir: dir,
}
n, errMigrate := migrate.Exec(db, e.Database, migrations, migrate.Up)
if errMigrate != nil {
return nil, fmt.Errorf("failed to apply up migrations: %s", errMigrate)
}
venom.Debug(ctx, "applied %d migrations\n", n)
}
// Load fixtures in the databases.
if err = loadFixtures(ctx, db, e.Files, e.Folder, getDialect(e.Database, e.SkipResetSequences), workdir); err != nil {
return nil, err
}
r := Result{Executor: e}
return r, nil
}
// ZeroValueResult return an empty implementation of this executor result
func (Executor) ZeroValueResult() interface{} {
return Result{}
}
// GetDefaultAssertions return the default assertions of the executor.
func (e Executor) GetDefaultAssertions() venom.StepAssertions {
return venom.StepAssertions{Assertions: []venom.Assertion{}}
}
// loadFixtures loads the fixtures in the database.
// It gives priority to the fixtures files found in folder,
// and switch to the list of files if no folder was specified.
func loadFixtures(ctx context.Context, db *sql.DB, files []string, folder string, dialect func(*fixtures.Loader) error, workdir string) error {
if folder != "" {
venom.Debug(ctx, "loading fixtures from folder %s\n", path.Join(workdir, folder))
loader, err := fixtures.New(
// By default the package refuse to load if the database
// does not contains "test" to avoid wiping a production db.
fixtures.DangerousSkipTestDatabaseCheck(),
fixtures.Database(db),
fixtures.Directory(path.Join(workdir, folder)),
dialect)
if err != nil {
return errors.Wrapf(err, "failed to create folder loader")
}
if err = loader.Load(); err != nil {
return errors.Wrapf(err, "failed to load fixtures from folder %q", path.Join(workdir, folder))
}
return nil
}
if len(files) != 0 {
venom.Debug(ctx, "loading fixtures from files: %v\n", files)
for i := range files {
files[i] = path.Join(workdir, files[i])
}
loader, err := fixtures.New(
// By default the package refuse to load if the database
// does not contains "test" to avoid wiping a production db.
fixtures.DangerousSkipTestDatabaseCheck(),
fixtures.Database(db),
fixtures.Files(files...),
dialect)
if err != nil {
return errors.Wrapf(err, "failed to create files loader")
}
if err = loader.Load(); err != nil {
return errors.Wrapf(err, "failed to load fixtures from files")
}
return nil
}
venom.Debug(ctx, "neither files or folder parameter was used\n")
return nil
}
func getDialect(name string, skipResetSequences bool) func(*fixtures.Loader) error {
switch name {
case "postgres":
return func(l *fixtures.Loader) error {
if err := fixtures.Dialect("postgresql")(l); err != nil {
return err
}
if skipResetSequences {
if err := fixtures.SkipResetSequences()(l); err != nil {
return err
}
}
return nil
}
case "mysql":
return fixtures.Dialect("mysql")
}
return nil
}