github.com/nagyist/migrate/v4@v4.14.6/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  		dt.TestLockAndUnlock(t, d)
    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 TestLockWorks(t *testing.T) {
   184  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   185  		ip, port, err := c.FirstPort()
   186  		if err != nil {
   187  			t.Fatal(err)
   188  		}
   189  
   190  		addr := mongoConnectionString(ip, port)
   191  		p := &Mongo{}
   192  		d, err := p.Open(addr)
   193  		if err != nil {
   194  			t.Fatal(err)
   195  		}
   196  		defer func() {
   197  			if err := d.Close(); err != nil {
   198  				t.Error(err)
   199  			}
   200  		}()
   201  
   202  		dt.TestRun(t, d, bytes.NewReader([]byte(`[{"insert":"hello","documents":[{"wild":"world"}]}]`)))
   203  
   204  		mc := d.(*Mongo)
   205  
   206  		err = mc.Lock()
   207  		if err != nil {
   208  			t.Fatal(err)
   209  		}
   210  		err = mc.Unlock()
   211  		if err != nil {
   212  			t.Fatal(err)
   213  		}
   214  
   215  		err = mc.Lock()
   216  		if err != nil {
   217  			t.Fatal(err)
   218  		}
   219  		err = mc.Unlock()
   220  		if err != nil {
   221  			t.Fatal(err)
   222  		}
   223  
   224  		// disable locking, validate wer can lock twice
   225  		mc.config.Locking.Enabled = false
   226  		err = mc.Lock()
   227  		if err != nil {
   228  			t.Fatal(err)
   229  		}
   230  		err = mc.Lock()
   231  		if err != nil {
   232  			t.Fatal(err)
   233  		}
   234  
   235  		// re-enable locking,
   236  		//try to hit a lock conflict
   237  		mc.config.Locking.Enabled = true
   238  		mc.config.Locking.Timeout = 1
   239  		err = mc.Lock()
   240  		if err != nil {
   241  			t.Fatal(err)
   242  		}
   243  		err = mc.Lock()
   244  		if err == nil {
   245  			t.Fatal("should have failed, mongo should be locked already")
   246  		}
   247  	})
   248  }
   249  
   250  func TestTransaction(t *testing.T) {
   251  	transactionSpecs := []dktesting.ContainerSpec{
   252  		{ImageName: "mongo:4", Options: dktest.Options{PortRequired: true, ReadyFunc: isReady,
   253  			Cmd: []string{"mongod", "--bind_ip_all", "--replSet", "rs0"}}},
   254  	}
   255  	dktesting.ParallelTest(t, transactionSpecs, func(t *testing.T, c dktest.ContainerInfo) {
   256  		ip, port, err := c.FirstPort()
   257  		if err != nil {
   258  			t.Fatal(err)
   259  		}
   260  
   261  		client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoConnectionString(ip, port)))
   262  		if err != nil {
   263  			t.Fatal(err)
   264  		}
   265  		err = client.Ping(context.TODO(), nil)
   266  		if err != nil {
   267  			t.Fatal(err)
   268  		}
   269  		//rs.initiate()
   270  		err = client.Database("admin").RunCommand(context.TODO(), bson.D{bson.E{Key: "replSetInitiate", Value: bson.D{}}}).Err()
   271  		if err != nil {
   272  			t.Fatal(err)
   273  		}
   274  		err = waitForReplicaInit(client)
   275  		if err != nil {
   276  			t.Fatal(err)
   277  		}
   278  		d, err := WithInstance(client, &Config{
   279  			DatabaseName: "testMigration",
   280  		})
   281  		if err != nil {
   282  			t.Fatal(err)
   283  		}
   284  		defer func() {
   285  			if err := d.Close(); err != nil {
   286  				t.Error(err)
   287  			}
   288  		}()
   289  		//We have to create collection
   290  		//transactions don't support operations with creating new dbs, collections
   291  		//Unique index need for checking transaction aborting
   292  		insertCMD := []byte(`[
   293  				{"create":"hello"},
   294  				{"createIndexes": "hello",
   295  					"indexes": [{
   296  						"key": {
   297  							"wild": 1
   298  						},
   299  						"name": "unique_wild",
   300  						"unique": true,
   301  						"background": true
   302  					}]
   303  			}]`)
   304  		err = d.Run(bytes.NewReader(insertCMD))
   305  		if err != nil {
   306  			t.Fatal(err)
   307  		}
   308  		testcases := []struct {
   309  			name            string
   310  			cmds            []byte
   311  			documentsCount  int64
   312  			isErrorExpected bool
   313  		}{
   314  			{
   315  				name: "success transaction",
   316  				cmds: []byte(`[{"insert":"hello","documents":[
   317  										{"wild":"world"},
   318  										{"wild":"west"},
   319  										{"wild":"natural"}
   320  									 ]
   321  								  }]`),
   322  				documentsCount:  3,
   323  				isErrorExpected: false,
   324  			},
   325  			{
   326  				name: "failure transaction",
   327  				//transaction have to be failure - duplicate unique key wild:west
   328  				//none of the documents should be added
   329  				cmds: []byte(`[{"insert":"hello","documents":[{"wild":"flower"}]},
   330  									{"insert":"hello","documents":[
   331  										{"wild":"cat"},
   332  										{"wild":"west"}
   333  									 ]
   334  								  }]`),
   335  				documentsCount:  3,
   336  				isErrorExpected: true,
   337  			},
   338  		}
   339  		for _, tcase := range testcases {
   340  			t.Run(tcase.name, func(t *testing.T) {
   341  				client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoConnectionString(ip, port)))
   342  				if err != nil {
   343  					t.Fatal(err)
   344  				}
   345  				err = client.Ping(context.TODO(), nil)
   346  				if err != nil {
   347  					t.Fatal(err)
   348  				}
   349  				d, err := WithInstance(client, &Config{
   350  					DatabaseName:    "testMigration",
   351  					TransactionMode: true,
   352  				})
   353  				if err != nil {
   354  					t.Fatal(err)
   355  				}
   356  				defer func() {
   357  					if err := d.Close(); err != nil {
   358  						t.Error(err)
   359  					}
   360  				}()
   361  				runErr := d.Run(bytes.NewReader(tcase.cmds))
   362  				if runErr != nil {
   363  					if !tcase.isErrorExpected {
   364  						t.Fatal(runErr)
   365  					}
   366  				}
   367  				documentsCount, err := client.Database("testMigration").Collection("hello").CountDocuments(context.TODO(), bson.M{})
   368  				if err != nil {
   369  					t.Fatal(err)
   370  				}
   371  				if tcase.documentsCount != documentsCount {
   372  					t.Fatalf("expected %d and actual %d documents count not equal. run migration error:%s", tcase.documentsCount, documentsCount, runErr)
   373  				}
   374  			})
   375  		}
   376  	})
   377  }
   378  
   379  type isMaster struct {
   380  	IsMaster bool `bson:"ismaster"`
   381  }
   382  
   383  func waitForReplicaInit(client *mongo.Client) error {
   384  	ticker := time.NewTicker(time.Second * 1)
   385  	defer ticker.Stop()
   386  	timeout, err := strconv.Atoi(os.Getenv("MIGRATE_TEST_MONGO_REPLICA_SET_INIT_TIMEOUT"))
   387  	if err != nil {
   388  		timeout = 30
   389  	}
   390  	timeoutTimer := time.NewTimer(time.Duration(timeout) * time.Second)
   391  	defer timeoutTimer.Stop()
   392  	for {
   393  		select {
   394  		case <-ticker.C:
   395  			var status isMaster
   396  			//Check that node is primary because
   397  			//during replica set initialization, the first node first becomes a secondary and then becomes the primary
   398  			//should consider that initialization is completed only after the node has become the primary
   399  			result := client.Database("admin").RunCommand(context.TODO(), bson.D{bson.E{Key: "isMaster", Value: 1}})
   400  			r, err := result.DecodeBytes()
   401  			if err != nil {
   402  				return err
   403  			}
   404  			err = bson.Unmarshal(r, &status)
   405  			if err != nil {
   406  				return err
   407  			}
   408  			if status.IsMaster {
   409  				return nil
   410  			}
   411  		case <-timeoutTimer.C:
   412  			return fmt.Errorf("replica init timeout")
   413  		}
   414  	}
   415  
   416  }