github.com/tooolbox/migrate/v4@v4.6.2-0.20200325001913-461b03b92064/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/tooolbox/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/tooolbox/migrate/v4/database/testing"
    27  	"github.com/tooolbox/migrate/v4/dktesting"
    28  	_ "github.com/tooolbox/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  }