github.com/seashell-org/golang-migrate/v4@v4.15.3-0.20220722221203-6ab6c6c062d1/database/mongodb/mongodb_test.go (about) 1 package mongodb 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 8 "log" 9 10 "io" 11 "os" 12 "strconv" 13 "testing" 14 "time" 15 16 "github.com/dhui/dktest" 17 migrate "github.com/seashell-org/golang-migrate/v4" 18 "go.mongodb.org/mongo-driver/bson" 19 "go.mongodb.org/mongo-driver/mongo" 20 "go.mongodb.org/mongo-driver/mongo/options" 21 22 dt "github.com/seashell-org/golang-migrate/v4/database/testing" 23 "github.com/seashell-org/golang-migrate/v4/dktesting" 24 25 _ "github.com/seashell-org/golang-migrate/v4/source/file" 26 ) 27 28 var ( 29 opts = dktest.Options{PortRequired: true, ReadyFunc: isReady} 30 // Supported versions: https://www.mongodb.com/support-policy 31 specs = []dktesting.ContainerSpec{ 32 {ImageName: "mongo:3.4", Options: opts}, 33 {ImageName: "mongo:3.6", Options: opts}, 34 {ImageName: "mongo:4.0", Options: opts}, 35 {ImageName: "mongo:4.2", Options: opts}, 36 } 37 ) 38 39 func mongoConnectionString(host, port string) string { 40 // there is connect option for excluding serverConnection algorithm 41 // it's let avoid errors with mongo replica set connection in docker container 42 return fmt.Sprintf("mongodb://%s:%s/testMigration?connect=direct", host, port) 43 } 44 45 func isReady(ctx context.Context, c dktest.ContainerInfo) bool { 46 ip, port, err := c.FirstPort() 47 if err != nil { 48 return false 49 } 50 51 client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoConnectionString(ip, port))) 52 if err != nil { 53 return false 54 } 55 defer func() { 56 if err := client.Disconnect(ctx); err != nil { 57 log.Println("close error:", err) 58 } 59 }() 60 61 if err = client.Ping(ctx, nil); err != nil { 62 switch err { 63 case io.EOF: 64 return false 65 default: 66 log.Println(err) 67 } 68 return false 69 } 70 return true 71 } 72 73 func Test(t *testing.T) { 74 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 75 ip, port, err := c.FirstPort() 76 if err != nil { 77 t.Fatal(err) 78 } 79 80 addr := mongoConnectionString(ip, port) 81 p := &Mongo{} 82 d, err := p.Open(addr) 83 if err != nil { 84 t.Fatal(err) 85 } 86 defer func() { 87 if err := d.Close(); err != nil { 88 t.Error(err) 89 } 90 }() 91 dt.TestNilVersion(t, d) 92 dt.TestLockAndUnlock(t, d) 93 dt.TestRun(t, d, bytes.NewReader([]byte(`[{"insert":"hello","documents":[{"wild":"world"}]}]`))) 94 dt.TestSetVersion(t, d) 95 dt.TestDrop(t, d) 96 }) 97 } 98 99 func TestMigrate(t *testing.T) { 100 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 101 ip, port, err := c.FirstPort() 102 if err != nil { 103 t.Fatal(err) 104 } 105 106 addr := mongoConnectionString(ip, port) 107 p := &Mongo{} 108 d, err := p.Open(addr) 109 if err != nil { 110 t.Fatal(err) 111 } 112 defer func() { 113 if err := d.Close(); err != nil { 114 t.Error(err) 115 } 116 }() 117 m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "", d) 118 if err != nil { 119 t.Fatal(err) 120 } 121 dt.TestMigrate(t, m) 122 }) 123 } 124 125 func TestWithAuth(t *testing.T) { 126 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 127 ip, port, err := c.FirstPort() 128 if err != nil { 129 t.Fatal(err) 130 } 131 132 addr := mongoConnectionString(ip, port) 133 p := &Mongo{} 134 d, err := p.Open(addr) 135 if err != nil { 136 t.Fatal(err) 137 } 138 defer func() { 139 if err := d.Close(); err != nil { 140 t.Error(err) 141 } 142 }() 143 createUserCMD := []byte(`[{"createUser":"deminem","pwd":"gogo","roles":[{"role":"readWrite","db":"testMigration"}]}]`) 144 err = d.Run(bytes.NewReader(createUserCMD)) 145 if err != nil { 146 t.Fatal(err) 147 } 148 testcases := []struct { 149 name string 150 connectUri string 151 isErrorExpected bool 152 }{ 153 {"right auth data", "mongodb://deminem:gogo@%s:%v/testMigration", false}, 154 {"wrong auth data", "mongodb://wrong:auth@%s:%v/testMigration", true}, 155 } 156 157 for _, tcase := range testcases { 158 t.Run(tcase.name, func(t *testing.T) { 159 mc := &Mongo{} 160 d, err := mc.Open(fmt.Sprintf(tcase.connectUri, ip, port)) 161 if err == nil { 162 defer func() { 163 if err := d.Close(); err != nil { 164 t.Error(err) 165 } 166 }() 167 } 168 169 switch { 170 case tcase.isErrorExpected && err == nil: 171 t.Fatalf("no error when expected") 172 case !tcase.isErrorExpected && err != nil: 173 t.Fatalf("unexpected error: %v", err) 174 } 175 }) 176 } 177 }) 178 } 179 180 func TestLockWorks(t *testing.T) { 181 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 182 ip, port, err := c.FirstPort() 183 if err != nil { 184 t.Fatal(err) 185 } 186 187 addr := mongoConnectionString(ip, port) 188 p := &Mongo{} 189 d, err := p.Open(addr) 190 if err != nil { 191 t.Fatal(err) 192 } 193 defer func() { 194 if err := d.Close(); err != nil { 195 t.Error(err) 196 } 197 }() 198 199 dt.TestRun(t, d, bytes.NewReader([]byte(`[{"insert":"hello","documents":[{"wild":"world"}]}]`))) 200 201 mc := d.(*Mongo) 202 203 err = mc.Lock() 204 if err != nil { 205 t.Fatal(err) 206 } 207 err = mc.Unlock() 208 if err != nil { 209 t.Fatal(err) 210 } 211 212 err = mc.Lock() 213 if err != nil { 214 t.Fatal(err) 215 } 216 err = mc.Unlock() 217 if err != nil { 218 t.Fatal(err) 219 } 220 221 // enable locking, 222 //try to hit a lock conflict 223 mc.config.Locking.Enabled = true 224 mc.config.Locking.Timeout = 1 225 err = mc.Lock() 226 if err != nil { 227 t.Fatal(err) 228 } 229 err = mc.Lock() 230 if err == nil { 231 t.Fatal("should have failed, mongo should be locked already") 232 } 233 }) 234 } 235 236 func TestTransaction(t *testing.T) { 237 transactionSpecs := []dktesting.ContainerSpec{ 238 {ImageName: "mongo:4", Options: dktest.Options{PortRequired: true, ReadyFunc: isReady, 239 Cmd: []string{"mongod", "--bind_ip_all", "--replSet", "rs0"}}}, 240 } 241 dktesting.ParallelTest(t, transactionSpecs, func(t *testing.T, c dktest.ContainerInfo) { 242 ip, port, err := c.FirstPort() 243 if err != nil { 244 t.Fatal(err) 245 } 246 247 client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoConnectionString(ip, port))) 248 if err != nil { 249 t.Fatal(err) 250 } 251 err = client.Ping(context.TODO(), nil) 252 if err != nil { 253 t.Fatal(err) 254 } 255 //rs.initiate() 256 err = client.Database("admin").RunCommand(context.TODO(), bson.D{bson.E{Key: "replSetInitiate", Value: bson.D{}}}).Err() 257 if err != nil { 258 t.Fatal(err) 259 } 260 err = waitForReplicaInit(client) 261 if err != nil { 262 t.Fatal(err) 263 } 264 d, err := WithInstance(client, &Config{ 265 DatabaseName: "testMigration", 266 }) 267 if err != nil { 268 t.Fatal(err) 269 } 270 defer func() { 271 if err := d.Close(); err != nil { 272 t.Error(err) 273 } 274 }() 275 //We have to create collection 276 //transactions don't support operations with creating new dbs, collections 277 //Unique index need for checking transaction aborting 278 insertCMD := []byte(`[ 279 {"create":"hello"}, 280 {"createIndexes": "hello", 281 "indexes": [{ 282 "key": { 283 "wild": 1 284 }, 285 "name": "unique_wild", 286 "unique": true, 287 "background": true 288 }] 289 }]`) 290 err = d.Run(bytes.NewReader(insertCMD)) 291 if err != nil { 292 t.Fatal(err) 293 } 294 testcases := []struct { 295 name string 296 cmds []byte 297 documentsCount int64 298 isErrorExpected bool 299 }{ 300 { 301 name: "success transaction", 302 cmds: []byte(`[{"insert":"hello","documents":[ 303 {"wild":"world"}, 304 {"wild":"west"}, 305 {"wild":"natural"} 306 ] 307 }]`), 308 documentsCount: 3, 309 isErrorExpected: false, 310 }, 311 { 312 name: "failure transaction", 313 //transaction have to be failure - duplicate unique key wild:west 314 //none of the documents should be added 315 cmds: []byte(`[{"insert":"hello","documents":[{"wild":"flower"}]}, 316 {"insert":"hello","documents":[ 317 {"wild":"cat"}, 318 {"wild":"west"} 319 ] 320 }]`), 321 documentsCount: 3, 322 isErrorExpected: true, 323 }, 324 } 325 for _, tcase := range testcases { 326 t.Run(tcase.name, func(t *testing.T) { 327 client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoConnectionString(ip, port))) 328 if err != nil { 329 t.Fatal(err) 330 } 331 err = client.Ping(context.TODO(), nil) 332 if err != nil { 333 t.Fatal(err) 334 } 335 d, err := WithInstance(client, &Config{ 336 DatabaseName: "testMigration", 337 TransactionMode: true, 338 }) 339 if err != nil { 340 t.Fatal(err) 341 } 342 defer func() { 343 if err := d.Close(); err != nil { 344 t.Error(err) 345 } 346 }() 347 runErr := d.Run(bytes.NewReader(tcase.cmds)) 348 if runErr != nil { 349 if !tcase.isErrorExpected { 350 t.Fatal(runErr) 351 } 352 } 353 documentsCount, err := client.Database("testMigration").Collection("hello").CountDocuments(context.TODO(), bson.M{}) 354 if err != nil { 355 t.Fatal(err) 356 } 357 if tcase.documentsCount != documentsCount { 358 t.Fatalf("expected %d and actual %d documents count not equal. run migration error:%s", tcase.documentsCount, documentsCount, runErr) 359 } 360 }) 361 } 362 }) 363 } 364 365 type isMaster struct { 366 IsMaster bool `bson:"ismaster"` 367 } 368 369 func waitForReplicaInit(client *mongo.Client) error { 370 ticker := time.NewTicker(time.Second * 1) 371 defer ticker.Stop() 372 timeout, err := strconv.Atoi(os.Getenv("MIGRATE_TEST_MONGO_REPLICA_SET_INIT_TIMEOUT")) 373 if err != nil { 374 timeout = 30 375 } 376 timeoutTimer := time.NewTimer(time.Duration(timeout) * time.Second) 377 defer timeoutTimer.Stop() 378 for { 379 select { 380 case <-ticker.C: 381 var status isMaster 382 //Check that node is primary because 383 //during replica set initialization, the first node first becomes a secondary and then becomes the primary 384 //should consider that initialization is completed only after the node has become the primary 385 result := client.Database("admin").RunCommand(context.TODO(), bson.D{bson.E{Key: "isMaster", Value: 1}}) 386 r, err := result.DecodeBytes() 387 if err != nil { 388 return err 389 } 390 err = bson.Unmarshal(r, &status) 391 if err != nil { 392 return err 393 } 394 if status.IsMaster { 395 return nil 396 } 397 case <-timeoutTimer.C: 398 return fmt.Errorf("replica init timeout") 399 } 400 } 401 402 }