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