github.com/bdollma-te/migrate/v4@v4.17.0-clickv2/database/rqlite/rqlite_test.go (about) 1 package rqlite 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "testing" 10 11 "github.com/dhui/dktest" 12 "github.com/rqlite/gorqlite" 13 "github.com/stretchr/testify/assert" 14 15 "github.com/bdollma-te/migrate/v4" 16 dt "github.com/bdollma-te/migrate/v4/database/testing" 17 "github.com/bdollma-te/migrate/v4/dktesting" 18 _ "github.com/bdollma-te/migrate/v4/source/file" 19 ) 20 21 var defaultPort uint16 = 4001 22 23 var opts = dktest.Options{ 24 Env: map[string]string{"NODE_ID": "1"}, 25 PortRequired: true, 26 ReadyFunc: isReady, 27 } 28 var specs = []dktesting.ContainerSpec{ 29 {ImageName: "rqlite/rqlite:7.21.4", Options: opts}, 30 {ImageName: "rqlite/rqlite:8.0.6", Options: opts}, 31 {ImageName: "rqlite/rqlite:8.11.1", Options: opts}, 32 {ImageName: "rqlite/rqlite:8.12.3", Options: opts}, 33 } 34 35 func isReady(ctx context.Context, c dktest.ContainerInfo) bool { 36 ip, port, err := c.Port(defaultPort) 37 if err != nil { 38 fmt.Println("error getting port") 39 return false 40 } 41 42 statusString := fmt.Sprintf("http://%s:%s/status", ip, port) 43 fmt.Println(statusString) 44 45 var readyResp struct { 46 Store struct { 47 Ready bool `json:"ready"` 48 } `json:"store"` 49 } 50 51 resp, err := http.Get(statusString) 52 if err != nil { 53 fmt.Println("error getting status") 54 return false 55 } 56 57 if resp.StatusCode != 200 { 58 fmt.Println("statusCode != 200") 59 return false 60 } 61 62 body, err := io.ReadAll(resp.Body) 63 if err != nil { 64 fmt.Println("error reading body") 65 return false 66 } 67 68 if err := json.Unmarshal(body, &readyResp); err != nil { 69 fmt.Println("error unmarshaling body") 70 return false 71 } 72 73 return readyResp.Store.Ready 74 } 75 76 func Test(t *testing.T) { 77 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 78 ip, port, err := c.Port(defaultPort) 79 assert.NoError(t, err) 80 81 connectString := fmt.Sprintf("rqlite://%s:%s?level=strong&disableClusterDiscovery=true&x-connect-insecure=true", ip, port) 82 t.Logf("DB connect string : %s\n", connectString) 83 84 r := &Rqlite{} 85 d, err := r.Open(connectString) 86 assert.NoError(t, err) 87 88 dt.Test(t, d, []byte("CREATE TABLE t (Qty int, Name string);")) 89 }) 90 } 91 92 func TestMigrate(t *testing.T) { 93 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 94 ip, port, err := c.Port(defaultPort) 95 assert.NoError(t, err) 96 97 connectString := fmt.Sprintf("rqlite://%s:%s?level=strong&disableClusterDiscovery=true&x-connect-insecure=true", ip, port) 98 t.Logf("DB connect string : %s\n", connectString) 99 100 driver, err := OpenURL(connectString) 101 assert.NoError(t, err) 102 defer func() { 103 if err := driver.Close(); err != nil { 104 return 105 } 106 }() 107 108 m, err := migrate.NewWithDatabaseInstance( 109 "file://./examples/migrations", 110 "ql", driver) 111 assert.NoError(t, err) 112 113 dt.TestMigrate(t, m) 114 }) 115 } 116 117 func TestBadConnectInsecureParam(t *testing.T) { 118 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 119 ip, port, err := c.Port(defaultPort) 120 assert.NoError(t, err) 121 122 connectString := fmt.Sprintf("rqlite://%s:%s?x-connect-insecure=foo", ip, port) 123 t.Logf("DB connect string : %s\n", connectString) 124 125 _, err = OpenURL(connectString) 126 assert.ErrorIs(t, err, ErrBadConfig) 127 }) 128 } 129 130 func TestBadProtocol(t *testing.T) { 131 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 132 ip, port, err := c.Port(defaultPort) 133 assert.NoError(t, err) 134 135 connectString := fmt.Sprintf("postgres://%s:%s/database", ip, port) 136 t.Logf("DB connect string : %s\n", connectString) 137 138 _, err = OpenURL(connectString) 139 assert.ErrorIs(t, err, ErrBadConfig) 140 }) 141 } 142 143 func TestNoConfig(t *testing.T) { 144 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 145 ip, port, err := c.Port(defaultPort) 146 assert.NoError(t, err) 147 148 // gorqlite expects http(s) schemes 149 connectString := fmt.Sprintf("http://%s:%s?level=strong&disableClusterDiscovery=true", ip, port) 150 t.Logf("DB connect string : %s\n", connectString) 151 db, err := gorqlite.Open(connectString) 152 assert.NoError(t, err) 153 154 _, err = WithInstance(db, nil) 155 assert.ErrorIs(t, err, ErrNilConfig) 156 }) 157 } 158 159 func TestWithInstanceEmptyConfig(t *testing.T) { 160 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 161 ip, port, err := c.Port(defaultPort) 162 assert.NoError(t, err) 163 164 // gorqlite expects http(s) schemes 165 connectString := fmt.Sprintf("http://%s:%s?level=strong&disableClusterDiscovery=true", ip, port) 166 t.Logf("DB connect string : %s\n", connectString) 167 db, err := gorqlite.Open(connectString) 168 assert.NoError(t, err) 169 170 driver, err := WithInstance(db, &Config{}) 171 assert.NoError(t, err) 172 173 defer func() { 174 if err := driver.Close(); err != nil { 175 t.Fatal(err) 176 } 177 }() 178 179 m, err := migrate.NewWithDatabaseInstance( 180 "file://./examples/migrations", 181 "ql", driver) 182 assert.NoError(t, err) 183 184 t.Log("UP") 185 err = m.Up() 186 assert.NoError(t, err) 187 188 _, err = db.QueryOne(fmt.Sprintf("SELECT * FROM %s", DefaultMigrationsTable)) 189 assert.NoError(t, err) 190 191 t.Log("DOWN") 192 err = m.Down() 193 assert.NoError(t, err) 194 }) 195 } 196 197 func TestMigrationTable(t *testing.T) { 198 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 199 ip, port, err := c.Port(defaultPort) 200 assert.NoError(t, err) 201 202 // gorqlite expects http(s) schemes 203 connectString := fmt.Sprintf("http://%s:%s?level=strong&disableClusterDiscovery=true", ip, port) 204 t.Logf("DB connect string : %s\n", connectString) 205 db, err := gorqlite.Open(connectString) 206 assert.NoError(t, err) 207 208 config := Config{MigrationsTable: "my_migration_table"} 209 driver, err := WithInstance(db, &config) 210 assert.NoError(t, err) 211 212 defer func() { 213 if err := driver.Close(); err != nil { 214 t.Fatal(err) 215 } 216 }() 217 218 m, err := migrate.NewWithDatabaseInstance( 219 "file://./examples/migrations", 220 "ql", driver) 221 assert.NoError(t, err) 222 223 t.Log("UP") 224 err = m.Up() 225 assert.NoError(t, err) 226 227 _, err = db.QueryOne(fmt.Sprintf("SELECT * FROM %s", config.MigrationsTable)) 228 assert.NoError(t, err) 229 230 _, err = db.WriteOne(`INSERT INTO pets (name, predator) VALUES ("franklin", true)`) 231 assert.NoError(t, err) 232 233 res, err := db.QueryOne(`SELECT name, predator FROM pets LIMIT 1`) 234 assert.NoError(t, err) 235 236 _ = res.Next() 237 238 // make sure we can use the migrated table 239 var petName string 240 var petPredator int 241 err = res.Scan(&petName, &petPredator) 242 assert.NoError(t, err) 243 assert.Equal(t, petName, "franklin") 244 assert.Equal(t, petPredator, 1) 245 246 t.Log("DOWN") 247 err = m.Down() 248 assert.NoError(t, err) 249 250 _, err = db.QueryOne(fmt.Sprintf("SELECT * FROM %s", config.MigrationsTable)) 251 assert.NoError(t, err) 252 }) 253 } 254 255 func TestParseUrl(t *testing.T) { 256 tests := []struct { 257 name string 258 passedUrl string 259 expectedUrl string 260 expectedConfig *Config 261 expectedErr string 262 }{ 263 { 264 "defaults", 265 "rqlite://localhost:4001", 266 "https://localhost:4001", 267 &Config{ConnectInsecure: DefaultConnectInsecure, MigrationsTable: DefaultMigrationsTable}, 268 "", 269 }, 270 { 271 "configure migration table", 272 "rqlite://localhost:4001?x-migrations-table=foo", 273 "https://localhost:4001", 274 &Config{ConnectInsecure: DefaultConnectInsecure, MigrationsTable: "foo"}, 275 "", 276 }, 277 { 278 "configure connect insecure", 279 "rqlite://localhost:4001?x-connect-insecure=true", 280 "http://localhost:4001", 281 &Config{ConnectInsecure: true, MigrationsTable: DefaultMigrationsTable}, 282 "", 283 }, 284 { 285 "invalid migration table", 286 "rqlite://localhost:4001?x-migrations-table=sqlite_bar", 287 "", 288 nil, 289 "invalid value for x-migrations-table: bad parameter", 290 }, 291 { 292 "invalid connect insecure", 293 "rqlite://localhost:4001?x-connect-insecure=baz", 294 "", 295 nil, 296 "invalid value for x-connect-insecure: bad parameter", 297 }, 298 { 299 "invalid url", 300 string([]byte{0x7f}), 301 "", 302 nil, 303 "parse \"\\x7f\": net/url: invalid control character in URL", 304 }, 305 } 306 for _, tt := range tests { 307 t.Run(tt.name, func(t *testing.T) { 308 actualUrl, actualConfig, actualErr := parseUrl(tt.passedUrl) 309 if tt.expectedUrl != "" { 310 assert.Equal(t, tt.expectedUrl, actualUrl.String()) 311 } else { 312 assert.Nil(t, actualUrl) 313 } 314 315 assert.Equal(t, tt.expectedConfig, actualConfig) 316 317 if tt.expectedErr == "" { 318 assert.NoError(t, actualErr) 319 } else { 320 assert.EqualError(t, actualErr, tt.expectedErr) 321 } 322 }) 323 } 324 }