diff --git a/mongorestore/mongorestore_txn_test.go b/mongorestore/mongorestore_txn_test.go index 116f89d1d..145d77462 100644 --- a/mongorestore/mongorestore_txn_test.go +++ b/mongorestore/mongorestore_txn_test.go @@ -7,18 +7,17 @@ package mongorestore import ( - "errors" "fmt" "os" "strings" "testing" - "github.com/google/go-cmp/cmp" "github.com/mongodb/mongo-tools/common/bsonutil" "github.com/mongodb/mongo-tools/common/db" "github.com/mongodb/mongo-tools/common/testtype" "github.com/mongodb/mongo-tools/common/testutil" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" ) @@ -39,23 +38,17 @@ type txnTestDataCase struct { func TestMongorestoreTxns(t *testing.T) { testtype.SkipUnlessTestType(t, testtype.IntegrationTestType) client, err := testutil.GetBareSession() - if err != nil { - t.Fatalf("No server available") - } + require.NoError(t, err, "must connect to the server") restore, err := getRestoreWithArgs() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err, "must build a restore instance") file := txnTestDataFilePre61 if restore.serverVersion.GTE(db.Version{6, 1, 0}) { file = txnTestDataFile61Plus } data, err := readTxnTestData(file) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err, "must read the transaction test data") // Create test collections (if they don't exist) and clear documents. for _, v := range data { @@ -63,37 +56,31 @@ func TestMongorestoreTxns(t *testing.T) { db := client.Database(parts[0]) coll := db.Collection(parts[1]) err := coll.Drop(t.Context()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err, "must drop the existing test collection") res := db.RunCommand(t.Context(), bson.D{{"create", parts[1]}}) - if res.Err() != nil { - t.Fatal(res.Err()) - } + require.NoError(t, res.Err(), "must create the test collection") } // Create a dump directory from transactions.json dumpPath := createTxnTestDataDir(t, data) - Convey("With a test MongoRestore", t, func() { - args := []string{ - OplogReplayOption, - DropOption, - dumpPath, - } - restore, err := getRestoreWithArgs(args...) - So(err, ShouldBeNil) - defer restore.Close() + args := []string{ + OplogReplayOption, + DropOption, + dumpPath, + } + restore, err = getRestoreWithArgs(args...) + require.NoError(t, err, "should build a restore instance from the dump path") + defer restore.Close() - result := restore.Restore() - So(result.Err, ShouldBeNil) + result := restore.Restore() + require.NoError(t, result.Err, "should restore without error") - for k, v := range data { - _, err = Println("postImageCheck for", k) - So(err, ShouldBeNil) - So(postImageCheck(t, client, v), ShouldBeNil) - } - }) + for k, v := range data { + t.Run(k, func(t *testing.T) { + postImageCheck(t, client, v) + }) + } } // createTxnTestDataDir constructs a dump directory with an oplog.bson @@ -114,9 +101,7 @@ func createTxnTestDataDir(t *testing.T, data txnTestDataMap) string { } err := dumpDir.Create() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err, "should create the dump directory") return dumpDir.Path() } @@ -140,54 +125,34 @@ func readTxnTestData(filename string) (txnTestDataMap, error) { return txnTestData, nil } -func postImageCheck(t *testing.T, client *mongo.Client, c *txnTestDataCase) error { +func postImageCheck(t *testing.T, client *mongo.Client, c *txnTestDataCase) { expected := make(map[int]bson.D) for _, v := range c.PostImage { id, err := bsonutil.FindIntByKey("_id", &v) - if err != nil { - return err - } + require.NoError(t, err, "should find the _id of each expected document") expected[id] = v } parts := strings.SplitN(c.NS, ".", 2) - db := client.Database(parts[0]) - coll := db.Collection(parts[1]) + coll := client.Database(parts[0]).Collection(parts[1]) cursor, err := coll.Find(t.Context(), bson.D{}) - if err != nil { - return err - } + require.NoError(t, err, "should query the restored collection") defer cursor.Close(t.Context()) + var docs []bson.D - err = cursor.All(t.Context(), &docs) - if err != nil { - return err - } + require.NoError(t, cursor.All(t.Context(), &docs), "should read every restored document") for _, got := range docs { id, err := bsonutil.FindIntByKey("_id", &got) - if err != nil { - return err - } + require.NoError(t, err, "should find the _id of each restored document") + want, ok := expected[id] - if !ok { - return fmt.Errorf("got unexpected document with _id '%d'", id) - } - if diff := cmp.Diff(got, want); diff != "" { - return errors.New(diff) - } - delete(expected, id) - } + require.True(t, ok, "should restore only expected documents, got _id %d", id) - // Check if all documents were found - if len(expected) != 0 { - var missing []int - for i := range expected { - missing = append(missing, i) - } - return fmt.Errorf("missing documents: %v", missing) + assert.Equal(t, want, got, "should restore document _id %d unchanged", id) + delete(expected, id) } - return nil + assert.Empty(t, expected, "should restore every expected document") } diff --git a/mongorestore/ns/ns_test.go b/mongorestore/ns/ns_test.go index f3cf746d9..9412cf85f 100644 --- a/mongorestore/ns/ns_test.go +++ b/mongorestore/ns/ns_test.go @@ -12,7 +12,8 @@ import ( "github.com/mongodb/mongo-tools/common/log" "github.com/mongodb/mongo-tools/common/options" "github.com/mongodb/mongo-tools/common/testtype" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func init() { @@ -25,124 +26,183 @@ func init() { func TestEscape(t *testing.T) { testtype.SkipUnlessTestType(t, testtype.UnitTestType) - Convey("with a few strings", t, func() { - So(Escape("(blah)"), ShouldEqual, "(blah)") - So(Escape(""), ShouldEqual, "") - So(Escape(`bl*h*\\`), ShouldEqual, `bl\*h\*\\\\`) - So(Escape("blah**"), ShouldEqual, `blah\*\*`) - }) + cases := []struct { + in string + expected string + }{ + {"(blah)", "(blah)"}, + {"", ""}, + {`bl*h*\\`, `bl\*h\*\\\\`}, + {"blah**", `blah\*\*`}, + } + + for _, tc := range cases { + assert.Equal(t, tc.expected, Escape(tc.in), "should escape %q", tc.in) + } } func TestUnescape(t *testing.T) { testtype.SkipUnlessTestType(t, testtype.UnitTestType) - Convey("with a few escaped strings", t, func() { - So(Unescape("(blah)"), ShouldEqual, "(blah)") - So(Unescape(""), ShouldEqual, "") - So(Unescape(`bl\*h\*\\\\`), ShouldEqual, `bl*h*\\`) - So(Unescape(`blah\*\*`), ShouldEqual, "blah**") - }) + cases := []struct { + in string + expected string + }{ + {"(blah)", "(blah)"}, + {"", ""}, + {`bl\*h\*\\\\`, `bl*h*\\`}, + {`blah\*\*`, "blah**"}, + } + + for _, tc := range cases { + assert.Equal(t, tc.expected, Unescape(tc.in), "should unescape %q", tc.in) + } } func TestReplacer(t *testing.T) { testtype.SkipUnlessTestType(t, testtype.UnitTestType) - Convey("with replacements", t, func() { - Convey(`'$db$.user$$' -> 'test.user$$_$db$', 'pr\*d\.*' -> 'st\*g\\ing.*'`, func() { - r, err := NewRenamer( - []string{"$db$.user$$", `pr\*d\\.*`}, - []string{"test.user$$_$db$", `st\*g\\ing.*`}, - ) - So(r, ShouldNotBeNil) - So(err, ShouldBeNil) - So(r.Get("stuff.user"), ShouldEqual, "test.user_stuff") - So(r.Get("stuff.users"), ShouldEqual, "test.users_stuff") - So(r.Get(`pr*d\.users`), ShouldEqual, `st*g\ing.users`) - So(r.Get(`pr*d\.turbo.encabulators`), ShouldEqual, `st*g\ing.turbo.encabulators`) - So(r.Get(`st*g\ing.turbo.encabulators`), ShouldEqual, `st*g\ing.turbo.encabulators`) - }) - Convey(`'$:)*$.us(?:2)er$?$' -> 'test.us(?:2)er$?$_$:)*$'`, func() { - r, err := NewRenamer( - []string{"$:)*$.us(?:2)er$?$"}, - []string{"test.us(?:2)er$?$_$:)*$"}, - ) - So(r, ShouldNotBeNil) - So(err, ShouldBeNil) - So(r.Get("stuff.us(?:2)er"), ShouldEqual, "test.us(?:2)er_stuff") - So(r.Get("stuff.us(?:2)ers"), ShouldEqual, "test.us(?:2)ers_stuff") - }) - Convey("'*.*' -> '*_test.*'", func() { - r, err := NewRenamer([]string{"*.*"}, []string{"*_test.*"}) - So(r, ShouldNotBeNil) - So(err, ShouldBeNil) - So(r.Get("stuff.user"), ShouldEqual, "stuff_test.user") - So(r.Get("stuff.users"), ShouldEqual, "stuff_test.users") - So(r.Get("prod.turbo.encabulators"), ShouldEqual, "prod_test.turbo.encabulators") - }) - Convey("special characters", func() { - r, err := NewRenamer([]string{`restaurants.cafés`, `ÿœz.tāx`, `normal.characters`}, - []string{`ÿœp.tāx`, `yes.tax`, `special.charâctęrs`}) - So(r, ShouldNotBeNil) - So(err, ShouldBeNil) - So(r.Get("restaurants.cafés"), ShouldEqual, "ÿœp.tāx") - So(r.Get("ÿœz.tāx"), ShouldEqual, "yes.tax") - So(r.Get("normal.characters"), ShouldEqual, "special.charâctęrs") - }) + t.Run("with replacements", func(t *testing.T) { + cases := []struct { + name string + from []string + to []string + expected map[string]string + }{ + { + name: `'$db$.user$$' -> 'test.user$$_$db$', 'pr\*d\.*' -> 'st\*g\\ing.*'`, + from: []string{"$db$.user$$", `pr\*d\\.*`}, + to: []string{"test.user$$_$db$", `st\*g\\ing.*`}, + expected: map[string]string{ + "stuff.user": "test.user_stuff", + "stuff.users": "test.users_stuff", + `pr*d\.users`: `st*g\ing.users`, + `pr*d\.turbo.encabulators`: `st*g\ing.turbo.encabulators`, + `st*g\ing.turbo.encabulators`: `st*g\ing.turbo.encabulators`, + }, + }, + { + name: "'$:)*$.us(?:2)er$?$' -> 'test.us(?:2)er$?$_$:)*$'", + from: []string{"$:)*$.us(?:2)er$?$"}, + to: []string{"test.us(?:2)er$?$_$:)*$"}, + expected: map[string]string{ + "stuff.us(?:2)er": "test.us(?:2)er_stuff", + "stuff.us(?:2)ers": "test.us(?:2)ers_stuff", + }, + }, + { + name: "'*.*' -> '*_test.*'", + from: []string{"*.*"}, + to: []string{"*_test.*"}, + expected: map[string]string{ + "stuff.user": "stuff_test.user", + "stuff.users": "stuff_test.users", + "prod.turbo.encabulators": "prod_test.turbo.encabulators", + }, + }, + { + name: "special characters", + from: []string{`restaurants.cafés`, `ÿœz.tāx`, `normal.characters`}, + to: []string{`ÿœp.tāx`, `yes.tax`, `special.charâctęrs`}, + expected: map[string]string{ + "restaurants.cafés": "ÿœp.tāx", + "ÿœz.tāx": "yes.tax", + "normal.characters": "special.charâctęrs", + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + r, err := NewRenamer(tc.from, tc.to) + require.NoError(t, err, "should build a renamer") + require.NotNil(t, r, "should return a renamer") + for in, expected := range tc.expected { + assert.Equal(t, expected, r.Get(in), "should rename %q", in) + } + }) + } }) - Convey("with invalid replacements", t, func() { - Convey("'$db$.user$db$' -> 'test.user-$db$'", func() { - _, err := NewRenamer([]string{"$db$.user$db$"}, []string{"test.user-$db$"}) - So(err, ShouldNotBeNil) - }) - Convey("'$db$.us$er$table$' -> 'test.user$table$_$db$'", func() { - _, err := NewRenamer([]string{"$db$.us$er$table$"}, []string{"test.user$table$_$db$"}) - So(err, ShouldNotBeNil) - }) + + t.Run("with invalid replacements", func(t *testing.T) { + cases := []struct{ from, to string }{ + {"$db$.user$db$", "test.user-$db$"}, + {"$db$.us$er$table$", "test.user$table$_$db$"}, + } + for _, tc := range cases { + _, err := NewRenamer([]string{tc.from}, []string{tc.to}) + assert.Error(t, err, "should reject replacement %q -> %q", tc.from, tc.to) + } }) } func TestMatcher(t *testing.T) { testtype.SkipUnlessTestType(t, testtype.UnitTestType) - Convey("with matcher", t, func() { - Convey(`'*.user*', 'pr\*d\.*'`, func() { - m, err := NewMatcher([]string{`*.user*`, `pr\*d\.*`}) - So(m, ShouldNotBeNil) - So(err, ShouldBeNil) - So(m.Has("stuff.user"), ShouldBeTrue) - So(m.Has("stuff.users"), ShouldBeTrue) - So(m.Has("pr*d.users"), ShouldBeTrue) - So(m.Has("pr*d.magic"), ShouldBeTrue) - So(m.Has(`pr*d\.magic`), ShouldBeFalse) - So(m.Has("prod.magic"), ShouldBeFalse) - So(m.Has("pr*d.turbo.encabulators"), ShouldBeTrue) - So(m.Has("st*ging.turbo.encabulators"), ShouldBeFalse) - }) - Convey("'*.*'", func() { - m, err := NewMatcher([]string{"*.*"}) - So(m, ShouldNotBeNil) - So(err, ShouldBeNil) - So(m.Has("stuff"), ShouldBeFalse) - So(m.Has("stuff.user"), ShouldBeTrue) - So(m.Has("stuff.users"), ShouldBeTrue) - So(m.Has("prod.turbo.encabulators"), ShouldBeTrue) - }) - Convey("special characters", func() { - m, err := NewMatcher([]string{`restaurants.cafés`, `ÿœp.tāx`}) - So(m, ShouldNotBeNil) - So(err, ShouldBeNil) - So(m.Has("restaurants.cafés"), ShouldBeTrue) - So(m.Has("ÿœp.tāx"), ShouldBeTrue) - }) + t.Run("with matcher", func(t *testing.T) { + cases := []struct { + name string + patterns []string + expected map[string]bool + }{ + { + name: `'*.user*', 'pr\*d\.*'`, + patterns: []string{`*.user*`, `pr\*d\.*`}, + expected: map[string]bool{ + "stuff.user": true, + "stuff.users": true, + "pr*d.users": true, + "pr*d.magic": true, + `pr*d\.magic`: false, + "prod.magic": false, + "pr*d.turbo.encabulators": true, + "st*ging.turbo.encabulators": false, + }, + }, + { + name: "'*.*'", + patterns: []string{"*.*"}, + expected: map[string]bool{ + "stuff": false, + "stuff.user": true, + "stuff.users": true, + "prod.turbo.encabulators": true, + }, + }, + { + name: "special characters", + patterns: []string{`restaurants.cafés`, `ÿœp.tāx`}, + expected: map[string]bool{ + "restaurants.cafés": true, + "ÿœp.tāx": true, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + m, err := NewMatcher(tc.patterns) + require.NoError(t, err, "should build a matcher") + require.NotNil(t, m, "should return a matcher") + for in, expected := range tc.expected { + assert.Equal( + t, + expected, + m.Has(in), + "should report %q as matching=%v", + in, + expected, + ) + } + }) + } }) - Convey("with invalid matcher", t, func() { - Convey("'$.user$'", func() { - _, err := NewMatcher([]string{"$.user$"}) - So(err, ShouldNotBeNil) - }) - Convey("'*.user$'", func() { - _, err := NewMatcher([]string{"*.user$"}) - So(err, ShouldNotBeNil) - }) + + t.Run("with invalid matcher", func(t *testing.T) { + cases := []string{"$.user$", "*.user$"} + for _, pattern := range cases { + _, err := NewMatcher([]string{pattern}) + assert.Error(t, err, "should reject pattern %q", pattern) + } }) } diff --git a/mongorestore/restore_test.go b/mongorestore/restore_test.go index 8cf0af726..17e55ce4c 100644 --- a/mongorestore/restore_test.go +++ b/mongorestore/restore_test.go @@ -7,13 +7,12 @@ package mongorestore import ( - "fmt" "slices" "testing" "github.com/mongodb/mongo-tools/common/idx" "github.com/mongodb/mongo-tools/common/testtype" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -73,26 +72,15 @@ func Test_removeDefaultIdIndex(t *testing.T) { } for _, curCase := range cases { - Convey( - fmt.Sprintf( - "Verifying that default _id indexes are removed when needed: %s", - curCase.Label, - ), - t, - func() { - expect := slices.Clone(curCase.Input) - if curCase.DefaultIdIndexAt >= 0 { - expect = slices.Delete( - expect, - curCase.DefaultIdIndexAt, - 1+curCase.DefaultIdIndexAt, - ) - } + t.Run(curCase.Label, func(t *testing.T) { + expect := slices.Clone(curCase.Input) + if curCase.DefaultIdIndexAt >= 0 { + expect = slices.Delete(expect, curCase.DefaultIdIndexAt, 1+curCase.DefaultIdIndexAt) + } - got, err := removeDefaultIdIndex(slices.Clone(curCase.Input)) - So(err, ShouldBeNil) - So(got, ShouldEqual, expect) - }, - ) + got, err := removeDefaultIdIndex(slices.Clone(curCase.Input)) + require.NoError(t, err, "should remove the default _id index without error") + require.Equal(t, expect, got, "should remove the default _id index") + }) } }