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