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