Skip to content

Commit 4cfc3ed

Browse files
committed
store db migrates in the database if it supports it
1 parent 67ef559 commit 4cfc3ed

File tree

6 files changed

+750
-0
lines changed

6 files changed

+750
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
package postgres
2+
3+
import (
4+
"database/sql"
5+
"testing"
6+
7+
"github.com/dhui/dktest"
8+
"github.com/golang-migrate/migrate/v4/database"
9+
"github.com/golang-migrate/migrate/v4/dktesting"
10+
_ "github.com/lib/pq"
11+
)
12+
13+
var storageSpecs = []dktesting.ContainerSpec{
14+
{ImageName: "postgres:13", Options: opts},
15+
}
16+
17+
func TestStorageMigrations(t *testing.T) {
18+
testStorageBasicOperations(t)
19+
}
20+
21+
func TestSyncMigrations(t *testing.T) {
22+
testSyncMultipleMigrations(t)
23+
}
24+
25+
func TestStorageSchemaUpgrade(t *testing.T) {
26+
testSchemaUpgrade(t)
27+
}
28+
29+
func TestStorageErrorHandling(t *testing.T) {
30+
testStorageErrorCases(t)
31+
}
32+
33+
func testStorageBasicOperations(t *testing.T) {
34+
dktesting.ParallelTest(t, storageSpecs, func(t *testing.T, c dktest.ContainerInfo) {
35+
d := setupPostgresDriver(t, c)
36+
defer closeDriver(t, d)
37+
38+
// Cast to storage driver
39+
storageDriver := castToStorageDriver(t, d)
40+
41+
// Test storing and retrieving migrations
42+
testUpScript := []byte("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name VARCHAR(255));")
43+
testDownScript := []byte("DROP TABLE test_table;")
44+
45+
// Store migration (both up and down)
46+
err := storageDriver.StoreMigration(1, testUpScript, testDownScript)
47+
if err != nil {
48+
t.Fatalf("Failed to store migration: %v", err)
49+
}
50+
51+
// Retrieve migration
52+
retrievedUp, retrievedDown, err := storageDriver.GetMigration(1)
53+
if err != nil {
54+
t.Fatalf("Failed to retrieve migration: %v", err)
55+
}
56+
57+
if string(retrievedUp) != string(testUpScript) {
58+
t.Errorf("Retrieved up migration doesn't match. Expected: %s, Got: %s", testUpScript, retrievedUp)
59+
}
60+
61+
if string(retrievedDown) != string(testDownScript) {
62+
t.Errorf("Retrieved down migration doesn't match. Expected: %s, Got: %s", testDownScript, retrievedDown)
63+
}
64+
65+
// Test getting stored migrations list
66+
versions, err := storageDriver.GetStoredMigrations()
67+
if err != nil {
68+
t.Fatalf("Failed to get stored migrations: %v", err)
69+
}
70+
71+
if len(versions) != 1 || versions[0] != 1 {
72+
t.Errorf("Expected stored migrations [1], got %v", versions)
73+
}
74+
})
75+
}
76+
77+
func testSyncMultipleMigrations(t *testing.T) {
78+
// This test would require setting up a source driver with multiple migrations
79+
// For now, we'll test the basic storage functionality only
80+
t.Skip("SyncMigrations requires source driver setup - testing basic storage instead")
81+
}
82+
83+
func testSchemaUpgrade(t *testing.T) {
84+
dktesting.ParallelTest(t, storageSpecs, func(t *testing.T, c dktest.ContainerInfo) {
85+
d := setupPostgresDriver(t, c)
86+
defer closeDriver(t, d)
87+
88+
// Get underlying sql.DB to manually create old schema
89+
pgDriver := d.(*Postgres)
90+
db := pgDriver.db
91+
92+
// Create the old schema format (without storage columns)
93+
_, err := db.Exec(`CREATE TABLE schema_migrations (
94+
version bigint NOT NULL PRIMARY KEY,
95+
dirty boolean NOT NULL
96+
)`)
97+
if err != nil {
98+
t.Fatalf("Failed to create old schema: %v", err)
99+
}
100+
101+
// Insert some existing migration records
102+
_, err = db.Exec(`INSERT INTO schema_migrations (version, dirty) VALUES (1, false), (2, false)`)
103+
if err != nil {
104+
t.Fatalf("Failed to insert existing records: %v", err)
105+
}
106+
107+
// Now access the storage driver which should trigger schema upgrade
108+
storageDriver := castToStorageDriver(t, d)
109+
110+
// Try to store a migration - this should work after schema upgrade
111+
err = storageDriver.StoreMigration(3, []byte("CREATE TABLE test_upgrade (id SERIAL);"), []byte("DROP TABLE test_upgrade;"))
112+
if err != nil {
113+
t.Fatalf("Failed to store migration after schema upgrade: %v", err)
114+
}
115+
116+
// Verify the schema has the new columns
117+
verifySchemaUpgrade(t, db)
118+
verifyExistingRecordsPreserved(t, storageDriver)
119+
})
120+
}
121+
122+
func testStorageErrorCases(t *testing.T) {
123+
dktesting.ParallelTest(t, storageSpecs, func(t *testing.T, c dktest.ContainerInfo) {
124+
d := setupPostgresDriver(t, c)
125+
defer closeDriver(t, d)
126+
127+
storageDriver := castToStorageDriver(t, d)
128+
129+
// Test retrieving non-existent migration
130+
_, _, err := storageDriver.GetMigration(999)
131+
if err == nil {
132+
t.Error("Expected error when retrieving non-existent migration")
133+
}
134+
135+
// Store a valid migration first
136+
err = storageDriver.StoreMigration(1, []byte("CREATE TABLE test (id SERIAL);"), []byte("DROP TABLE test;"))
137+
if err != nil {
138+
t.Fatalf("Failed to store valid migration: %v", err)
139+
}
140+
141+
// Test storing duplicate migration (should update, not error)
142+
err = storageDriver.StoreMigration(1, []byte("CREATE TABLE test_updated (id SERIAL);"), []byte("DROP TABLE test_updated;"))
143+
if err != nil {
144+
t.Errorf("Unexpected error when updating existing migration: %v", err)
145+
}
146+
147+
// Verify the migration was updated
148+
upScript, _, err := storageDriver.GetMigration(1)
149+
if err != nil {
150+
t.Fatalf("Failed to retrieve updated migration: %v", err)
151+
}
152+
153+
expected := "CREATE TABLE test_updated (id SERIAL);"
154+
if string(upScript) != expected {
155+
t.Errorf("Migration was not updated. Expected: %s, Got: %s", expected, upScript)
156+
}
157+
})
158+
}
159+
160+
// Helper functions
161+
162+
func setupPostgresDriver(t *testing.T, c dktest.ContainerInfo) database.Driver {
163+
ip, port, err := c.FirstPort()
164+
if err != nil {
165+
t.Fatal(err)
166+
}
167+
168+
p := &Postgres{}
169+
addr := pgConnectionString(ip, port)
170+
171+
d, err := p.Open(addr)
172+
if err != nil {
173+
t.Fatal(err)
174+
}
175+
return d
176+
}
177+
178+
func closeDriver(t *testing.T, d database.Driver) {
179+
if err := d.Close(); err != nil {
180+
t.Error(err)
181+
}
182+
}
183+
184+
func castToStorageDriver(t *testing.T, d database.Driver) database.MigrationStorageDriver {
185+
storageDriver, ok := d.(database.MigrationStorageDriver)
186+
if !ok {
187+
t.Fatal("Postgres driver does not implement MigrationStorageDriver interface")
188+
}
189+
return storageDriver
190+
}
191+
192+
func verifySchemaUpgrade(t *testing.T, db *sql.DB) {
193+
rows, err := db.Query(`SELECT column_name FROM information_schema.columns
194+
WHERE table_name = 'schema_migrations' ORDER BY column_name`)
195+
if err != nil {
196+
t.Fatalf("Failed to query schema columns: %v", err)
197+
}
198+
defer rows.Close()
199+
200+
var columns []string
201+
for rows.Next() {
202+
var col string
203+
if err := rows.Scan(&col); err != nil {
204+
t.Fatalf("Failed to scan column name: %v", err)
205+
}
206+
columns = append(columns, col)
207+
}
208+
209+
expectedColumns := []string{"created_at", "dirty", "down_script", "up_script", "version"}
210+
if len(columns) != len(expectedColumns) {
211+
t.Errorf("Expected %d columns, got %d: %v", len(expectedColumns), len(columns), columns)
212+
}
213+
214+
for i, expected := range expectedColumns {
215+
if i >= len(columns) || columns[i] != expected {
216+
t.Errorf("Column mismatch at position %d. Expected: %s, Got: %v", i, expected, columns)
217+
break
218+
}
219+
}
220+
}
221+
222+
func verifyExistingRecordsPreserved(t *testing.T, storageDriver database.MigrationStorageDriver) {
223+
versions, err := storageDriver.GetStoredMigrations()
224+
if err != nil {
225+
t.Fatalf("Failed to get stored migrations: %v", err)
226+
}
227+
228+
if len(versions) < 3 {
229+
t.Errorf("Expected at least 3 migrations after upgrade, got %d", len(versions))
230+
}
231+
}

database/postgres/postgres_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ func Test(t *testing.T) {
100100
t.Run("testWithInstanceConcurrent", testWithInstanceConcurrent)
101101
t.Run("testWithConnection", testWithConnection)
102102

103+
// Storage functionality tests
104+
t.Run("TestStorageMigrations", TestStorageMigrations)
105+
t.Run("TestSyncMigrations", TestSyncMigrations)
106+
t.Run("TestStorageSchemaUpgrade", TestStorageSchemaUpgrade)
107+
t.Run("TestStorageErrorHandling", TestStorageErrorHandling)
108+
103109
t.Cleanup(func() {
104110
for _, spec := range specs {
105111
t.Log("Cleaning up ", spec.ImageName)

0 commit comments

Comments
 (0)