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  }