forked from golang-migrate/migrate
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvars_test.go
More file actions
182 lines (160 loc) · 5.45 KB
/
Copy pathvars_test.go
File metadata and controls
182 lines (160 loc) · 5.45 KB
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
package migrate
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"strings"
"sync"
"testing"
dStub "github.com/AccelByte/migrate/v4/database/stub"
"github.com/AccelByte/migrate/v4/source"
_ "github.com/AccelByte/migrate/v4/source/file"
)
func TestSetSourceVarsUnsupportedDriver(t *testing.T) {
// Unlike an unsupported profile this must be fatal, not advisory:
// silently skipping expansion ships literal {{ … }} to the database.
m, err := New("stub://", "stub://")
if err != nil {
t.Fatal(err)
}
defer func() { _, _ = m.Close() }()
err = m.SetSourceVars(source.VarConfig{Vars: map[string]string{"A": "1"}})
if !errors.Is(err, ErrSourceVarsUnsupported) {
t.Fatalf("SetSourceVars = %v, want ErrSourceVarsUnsupported", err)
}
}
func TestSetSourceVarsThroughNewWithDatabaseInstance(t *testing.T) {
// The shape the in-process integration uses: the constructor opens the
// source itself, and both mutators are available on the returned value.
dir := t.TempDir()
write := func(name, body string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o600); err != nil {
t.Fatal(err)
}
}
write("migrate.vars.json", `{"version": 1, "vars": [
{"name": "NAMESPACE", "pattern": "^[a-z]+$"},
{"name": "TTL_SECONDS", "default": "604800"}
]}`)
write("1_init.up.json", `{"create": "{{ .NAMESPACE }}_users", "ttl": {{ .TTL_SECONDS }}}`)
write("1_init.sharded.up.json", `{"create": "{{ .NAMESPACE }}_users_sharded"}`)
m, err := New("file://"+dir, "stub://")
if err != nil {
t.Fatal(err)
}
defer func() { _, _ = m.Close() }()
// Order matters: the profile selects the file set variables are
// validated against.
if !m.SetSourceProfile("sharded") {
t.Fatal("SetSourceProfile returned false")
}
if err := m.SetSourceVars(source.VarConfig{Vars: map[string]string{"NAMESPACE": "acme"}}); err != nil {
t.Fatalf("SetSourceVars: %v", err)
}
r, _, err := m.sourceDrv.ReadUp(1)
if err != nil {
t.Fatalf("ReadUp: %v", err)
}
defer func() { _ = r.Close() }()
body, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if want := `{"create": "acme_users_sharded"}`; string(body) != want {
t.Errorf("ReadUp = %q, want %q", body, want)
}
}
// TestVarsEndToEndThroughUp is the only test that exercises the whole
// pipeline: SetSourceVars, prefetch, and the database driver. It asserts what
// actually reaches the database, which no source-level test can.
func TestVarsEndToEndThroughUp(t *testing.T) {
dir := t.TempDir()
write := func(name, body string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o600); err != nil {
t.Fatal(err)
}
}
write("migrate.vars.json", `{"version": 1, "vars": [
{"name": "NAMESPACE", "pattern": "^[a-z]+$"},
{"name": "TTL_SECONDS", "default": "604800", "pattern": "^[0-9]+$"}
]}`)
write("1_init.up.sql", "CREATE TABLE {{ .NAMESPACE }}_users (id INT);")
write("1_init.down.sql", "DROP TABLE {{ .NAMESPACE }}_users;")
write("2_ttl.up.sql", "ALTER TABLE {{ .NAMESPACE }}_users SET TTL {{ .TTL_SECONDS }};")
// An empty body must render to an empty body, not fail.
write("3_noop.up.sql", "")
m, err := New("file://"+dir, "stub://")
if err != nil {
t.Fatal(err)
}
defer func() { _, _ = m.Close() }()
logged := &captureLog{}
m.Log = logged
if err := m.SetSourceVars(source.VarConfig{Vars: map[string]string{"NAMESPACE": "acme"}}); err != nil {
t.Fatalf("SetSourceVars: %v", err)
}
if err := m.Up(); err != nil {
t.Fatalf("Up: %v", err)
}
dbDrv := m.databaseDrv.(*dStub.Stub)
want := []string{
"CREATE TABLE acme_users (id INT);",
"ALTER TABLE acme_users SET TTL 604800;",
"",
}
if !reflect.DeepEqual(dbDrv.MigrationSequence, want) {
t.Errorf("statements reaching the database =\n%q\nwant\n%q", dbDrv.MigrationSequence, want)
}
// Expansion adds no log output of its own. Values come from deploy
// config, versioned alongside the deploy, so a render log would restate
// what is already recorded — and put secrets in log streams.
for _, banned := range []string{"variable", "expansion", "acme", "{{"} {
if strings.Contains(logged.String(), banned) {
t.Errorf("log mentions %q; expansion should be silent:\n%s", banned, logged.String())
}
}
// Down renders too, from the same validated templates.
if err := m.Down(); err != nil {
t.Fatalf("Down: %v", err)
}
if got := dbDrv.MigrationSequence[len(dbDrv.MigrationSequence)-1]; got != "DROP TABLE acme_users;" {
t.Errorf("last down statement = %q", got)
}
}
// captureLog collects everything migrate logs. It is mutex-guarded because
// migrate logs from the prefetch goroutine as well as the caller's, which is
// why migrate.Logger implementations must be safe for concurrent use.
type captureLog struct {
mu sync.Mutex
b strings.Builder
}
func (l *captureLog) Printf(format string, v ...interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
fmt.Fprintf(&l.b, format, v...)
}
func (l *captureLog) Verbose() bool { return true }
func (l *captureLog) String() string {
l.mu.Lock()
defer l.mu.Unlock()
return l.b.String()
}
func TestSetSourceVarsNoManifestIsTheSentinel(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "1_init.up.sql"), []byte("SELECT 1;"), 0o600); err != nil {
t.Fatal(err)
}
m, err := New("file://"+dir, "stub://")
if err != nil {
t.Fatal(err)
}
defer func() { _, _ = m.Close() }()
if err := m.SetSourceVars(source.VarConfig{}); !errors.Is(err, source.ErrNoManifest) {
t.Fatalf("SetSourceVars = %v, want source.ErrNoManifest", err)
}
}