diff --git a/README-ru.md b/README-ru.md index d54baa0..be71814 100644 --- a/README-ru.md +++ b/README-ru.md @@ -6,7 +6,7 @@ Gonkey протестирует ваши сервисы, используя их - работает с REST/JSON API - проверка API сервиса на соответствие OpenAPI-спеке -- заполнение БД сервиса данными из фикстур (поддерживается PostgreSQL, MySQL, Aerospike, Redis) +- заполнение БД сервиса данными из фикстур (поддерживается PostgreSQL, MySQL, Aerospike, Redis, MongoDB) - моки для имитации внешних сервисов - можно подключить к проекту как библиотеку и запускать вместе с юнит-тестами - запись результата тестов в виде отчета [Allure](http://allure.qatools.ru/) @@ -35,6 +35,7 @@ Gonkey протестирует ваши сервисы, используя их - [Выражения](#выражения) - [Aerospike](#aerospike) - [Redis](#redis) + - [MongoDB](#mongodb) - [Моки](#моки) - [Запуск моков при использовании gonkey как библиотеки](#запуск-моков-при-использовании-gonkey-как-библиотеки) - [Описание моков в файле с тестом](#описание-моков-в-файле-с-тестом) @@ -53,17 +54,18 @@ Gonkey протестирует ваши сервисы, используя их ## Использование консольной утилиты -Для тестирование сервиса, размещенного на удаленном хосте, используйте gonkey как консольную утилиту. +Для тестирования сервиса, размещенного на удаленном хосте, используйте gonkey как консольную утилиту. `./gonkey -host <...> -tests <...> [-spec <...>] [-db_dsn <...> -fixtures <...>] [-allure] [-v]` - `-spec <...>` путь к файлу или URL со swagger-спецификацией сервиса - `-host <...>` хост:порт сервиса - `-tests <...>` файл или директория с тестами -- `-db-type <...>` - тип базы данных. В данный момент поддерживается PostgreSQL, Aerospike, Redis. +- `-db-type <...>` - тип базы данных. В данный момент поддерживается PostgreSQL, Aerospike, Redis, MongoDB. - `-db_dsn <...>` dsn для вашей тестовой SQL базы данных (бд будет очищена перед наполнением!), поддерживается только PostgreSQL - `-aerospike_host <...>` при использовании Aerospike - URL для подключения к нему в формате `host:port/namespace` - `-redis_url <...>` при использовании Redis - адрес для подключения к Redis, например `redis://user:password@localhost:6789/1?dial_timeout=1&db=1&read_timeout=6s&max_retries=2` +- `-mongo_dsn <...>` при использовании MongoDB - URL для подключения в формате `mongodb://user:password@host:port` - `-fixtures <...>` директория с вашими фикстурами - `-allure` генерировать allure-отчет - `-v` подробный вывод @@ -876,6 +878,50 @@ databases: value: value4 ``` +### MongoDB + +Для того, чтобы подключить MongoDB необходимо: +- Для CLI-версии: использовать флаги `-db-type mongo` и `mongo_dsn { connectionString }`; +- Для Package-версии: при конфигурации раннера установить `DbType: fixtures.Mongo` и пробросить mongo клиент `Mongo: {mongo client}`. + +Формат файлов с фикстурами для Mongo: +```yaml +collections: + collection1: + - field1: "value1" + field2: 1 + - field1: "value2" + field2: 2 + field3: 2.569947773654566 + collection2: + - field4: false + field5: null + field1: '"' + - field1: "'" + field5: + - 1 + - '2' +``` + +Если используются разные базы данных: + +```yaml +collections: + database1.collection1: + - f1: value1 + f2: value2 + + database2.collection2: + - f1: value3 + f2: value4 + + collection3: + - f1: value5 + f2: value6 +``` + +Оператор `eval` не поддерживается. + ## Моки Чтобы для тестов имитировать ответы от внешних сервисов, применяются моки. diff --git a/README.md b/README.md index 32333d8..7213bd8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Capabilities: - works with REST/JSON API - tests service API for compliance with OpenAPI-specs -- seeds the DB with fixtures data (supports PostgreSQL, MySQL, Aerospike, Redis) +- seeds the DB with fixtures data (supports PostgreSQL, MySQL, Aerospike, Redis, MongoDB) - provides mocks for external services - can be used as a library and ran together with unit-tests - stores the results as an [Allure](http://allure.qatools.ru/) report @@ -37,6 +37,7 @@ Capabilities: - [Expressions](#expressions) - [Aerospike](#aerospike) - [Redis](#redis) + - [MongoDB](#mongodb) - [Mocks](#mocks) - [Running mocks while using gonkey as a library](#running-mocks-while-using-gonkey-as-a-library) - [Mocks definition in the test file](#mocks-definition-in-the-test-file) @@ -62,8 +63,9 @@ To test a service located on a remote host, use gonkey as a console util. - `-spec <...>` path to a file or URL with the swagger-specs for the service - `-host <...>` service host:port - `-tests <...>` test file or directory -- `-db-type <...>` - database type. PostgreSQL, Aerospike, Redis are currently supported. +- `-db-type <...>` - database type. PostgreSQL, Aerospike, Redis, Mongo are currently supported. - `-aerospike_host <...>` when using Aerospike - connection URL in a form of `host:port/namespace` +- `-mongo_dsn <...>` when using MongoDB - connection URL in a form of `mongodb://user:password@host:port` - `-redis_url <...>` when using Redis - connection address, for example `redis://user:password@localhost:6789/1?dial_timeout=1&db=1&read_timeout=6s&max_retries=2` - `-db_dsn <...>` DSN for the test DB (the DB will be cleared before seeding!), supports only PostgreSQL - `-fixtures <...>` fixtures directory @@ -879,6 +881,49 @@ databases: value: value4 ``` +### MongoDB + +To connect to MongoDB, you need to: +- For the CLI-version: use the flags -db-type mongo and mongo_dsn {connectionString}; +- For the Package-version: when configuring the runner, set DbType: fixtures.Mongo and pass the MongoDB client as Mongo: {mongo client}. + +The format of fixture files for MongoDB: +```yaml +collections: + collection1: + - field1: "value1" + field2: 1 + - field1: "value2" + field2: 2 + field3: 2.569947773654566 + collection2: + - field4: false + field5: null + field1: '"' + - field1: "'" + field5: + - 1 + - '2' +``` + +If you are using different databases: +```yaml +collections: + database1.collection1: + - f1: value1 + f2: value2 + + database2.collection2: + - f1: value3 + f2: value4 + + collection3: + - f1: value5 + f2: value6 +``` + +The `eval` operator is not supported. + ## Mocks In order to imitate responses from external services, use mocks. diff --git a/fixtures/loader.go b/fixtures/loader.go index 24b56ba..aaea365 100644 --- a/fixtures/loader.go +++ b/fixtures/loader.go @@ -2,6 +2,8 @@ package fixtures import ( "database/sql" + "github.com/lamoda/gonkey/fixtures/mongo" + mongoAdapter "github.com/lamoda/gonkey/storage/mongo" "strings" _ "github.com/lib/pq" @@ -20,6 +22,7 @@ const ( Aerospike Redis CustomLoader // using external loader if gonkey used as a library + Mongo ) const ( @@ -27,11 +30,13 @@ const ( MysqlParam = "mysql" AerospikeParam = "aerospike" RedisParam = "redis" + MongoParam = "mongo" ) type Config struct { DB *sql.DB Aerospike *aerospikeClient.Client + Mongo *mongoAdapter.Client DbType DbType Location string Debug bool @@ -67,6 +72,12 @@ func NewLoader(cfg *Config) Loader { location, cfg.Debug, ) + case Mongo: + loader = mongo.New( + cfg.Mongo, + location, + cfg.Debug, + ) default: if cfg.FixtureLoader != nil { return cfg.FixtureLoader @@ -87,6 +98,8 @@ func FetchDbType(dbType string) DbType { return Aerospike case RedisParam: return Redis + case MongoParam: + return Mongo default: panic("unknown db type param") } diff --git a/fixtures/mongo/mongo.go b/fixtures/mongo/mongo.go new file mode 100644 index 0000000..1d72145 --- /dev/null +++ b/fixtures/mongo/mongo.go @@ -0,0 +1,409 @@ +package mongo + +import ( + "encoding/json" + "errors" + "fmt" + "gopkg.in/yaml.v2" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" +) + +type mongoClient interface { + Truncate(database string, collection string) error + InsertDocuments(database string, collection string, documents []map[string]interface{}) ([]map[string]interface{}, error) +} + +type LoaderMongo struct { + client mongoClient + location string + debug bool +} + +type document map[string]interface{} + +type collection []document + +type documentsDict map[string]document + +type fixture struct { + Inherits []string + Collections yaml.MapSlice + Templates yaml.MapSlice +} + +type loadedCollection struct { + name collectionName + documents collection +} + +type collectionName struct { + name string + database string +} + +func newCollectionName(source string) collectionName { + parts := strings.SplitN(source, ".", 2) + + if len(parts) == 1 { + parts = append(parts, parts[0]) + parts[0] = "public" + } else if parts[0] == "" { + parts[0] = "public" + } + + cn := collectionName{database: parts[0], name: parts[1]} + return cn +} + +func (t *collectionName) getFullName() string { + return fmt.Sprintf("\"%s\".\"%s\"", t.database, t.name) +} + +type loadContext struct { + files []string + collections []loadedCollection + refsDefinition documentsDict + refsInserted documentsDict +} + +func New(client mongoClient, location string, debug bool) *LoaderMongo { + return &LoaderMongo{ + client: client, + location: location, + debug: debug, + } +} + +func (f *LoaderMongo) Load(names []string) error { + ctx := loadContext{ + refsDefinition: make(documentsDict), + refsInserted: make(documentsDict), + } + + // gather data from files + for _, name := range names { + if err := f.loadFile(name, &ctx); err != nil { + return fmt.Errorf("unable to load fixture %s: %s", name, err.Error()) + } + } + + return f.loadCollections(&ctx) +} + +func (f *LoaderMongo) loadFile(name string, ctx *loadContext) error { + candidates := []string{ + filepath.Join(f.location, name), + filepath.Join(f.location, name, ".yml"), + filepath.Join(f.location, name, ".yaml"), + } + + var err error + var file string + for _, candidate := range candidates { + if _, err = os.Stat(candidate); err == nil { + file = candidate + break + } + } + if err != nil { + return err + } + + // skip previously loaded files + if inArray(file, ctx.files) { + return nil + } + + if f.debug { + fmt.Println("Loading", file) + } + + data, err := ioutil.ReadFile(file) + if err != nil { + return err + } + + ctx.files = append(ctx.files, file) + return f.loadYml(data, ctx) +} + +func (f *LoaderMongo) loadYml(data []byte, ctx *loadContext) error { + // read yml into struct + var loadedFixture fixture + if err := yaml.Unmarshal(data, &loadedFixture); err != nil { + return err + } + + // load inherits + for _, inheritFile := range loadedFixture.Inherits { + if err := f.loadFile(inheritFile, ctx); err != nil { + return err + } + } + + // loadedFixture.templates + // yaml.MapSlice{ + // string => yaml.MapSlice{ --- template name + // string => interface{} --- field name: value + // } + // } + for _, template := range loadedFixture.Templates { + name := template.Key.(string) + if _, ok := ctx.refsDefinition[name]; ok { + return fmt.Errorf("unable to load template %s: duplicating ref name", name) + } + + fields := template.Value.(yaml.MapSlice) + doc := make(document, len(fields)) + for _, field := range fields { + key := field.Key.(string) + value, _ := field.Value.(interface{}) + doc[key] = value + } + + if base, ok := doc["$extend"]; ok { + base := base.(string) + baseDoc, err := f.resolveReference(ctx.refsDefinition, base) + if err != nil { + return err + } + for k, v := range doc { + baseDoc[k] = v + } + doc = baseDoc + } + ctx.refsDefinition[name] = doc + + if f.debug { + rowJson, _ := json.Marshal(doc) + fmt.Printf("Populating ref %s as %s from template\n", name, string(rowJson)) + } + } + + // loadedFixture.collections + // yaml.MapSlice{ + // string => []interface{ --- collection name + // yaml.MapSlice{ --- document + // string => interface{} --- field name: value + // } + // } + // } + for _, sourceCollection := range loadedFixture.Collections { + sourceDocuments, ok := sourceCollection.Value.([]interface{}) + if !ok { + return errors.New("expected array at root level") + } + + documents := make(collection, len(sourceDocuments)) + for i := range sourceDocuments { + sourceFields := sourceDocuments[i].(yaml.MapSlice) + fields := make(document, len(sourceFields)) + for j := range sourceFields { + fields[sourceFields[j].Key.(string)] = sourceFields[j].Value + } + documents[i] = fields + } + + lc := loadedCollection{ + name: newCollectionName(sourceCollection.Key.(string)), + documents: documents, + } + ctx.collections = append(ctx.collections, lc) + } + + return nil +} + +func (f *LoaderMongo) loadCollections(ctx *loadContext) error { + // truncate first + if err := f.truncateCollections(ctx.collections); err != nil { + return err + } + + // then load data + for _, cl := range ctx.collections { + if len(cl.documents) == 0 { + continue + } + + if err := f.loadCollection(ctx, cl); err != nil { + return fmt.Errorf("failed to load collection '%s' because:\n%s", cl.name.getFullName(), err) + } + } + + return nil +} + +// truncateCollections truncates collection +func (f *LoaderMongo) truncateCollections(collections []loadedCollection) error { + truncatedCollections := make(map[string]bool) + for _, cl := range collections { + if _, ok := truncatedCollections[cl.name.getFullName()]; ok { + // already truncated + continue + } + + if err := f.client.Truncate(cl.name.database, cl.name.name); err != nil { + return err + } + + truncatedCollections[cl.name.getFullName()] = true + } + + return nil +} + +func (f *LoaderMongo) loadCollection(ctx *loadContext, cl loadedCollection) error { + // $extend keyword allows, to import values from a named row + for i, doc := range cl.documents { + if base, ok := doc["$extend"]; ok { + baseName := base.(string) + baseDoc, err := f.resolveReference(ctx.refsDefinition, baseName) + if err != nil { + return err + } + + for k, v := range doc { + baseDoc[k] = v + } + + cl.documents[i] = baseDoc + } + } + + query, err := f.buildInsertQuery(ctx, cl) + if err != nil { + return err + } + insertedDocs, err := f.client.InsertDocuments(cl.name.database, cl.name.name, query) + if err != nil { + return err + } + + // reading results + // here I assume that returning rows go in the same + // order as values were passed to INSERT statement + for i, doc := range cl.documents { + if name, ok := doc["$name"]; ok { + name := name.(string) + if _, ok := ctx.refsDefinition[name]; ok { + return fmt.Errorf("duplicating ref name %s", name) + } + // add to references + ctx.refsDefinition[name] = doc + if f.debug { + docJson, _ := json.Marshal(doc) + fmt.Printf("Populating ref %s as %s from doc definition\n", name, string(docJson)) + } + values := insertedDocs[i] + ctx.refsInserted[name] = values + if f.debug { + valuesJson, _ := json.Marshal(values) + fmt.Printf("Populating ref %s as %s from inserted values\n", name, string(valuesJson)) + } + } + } + + return nil +} + +// buildInsertQuery builds query for data insertion +// based on values read from yaml +func (f *LoaderMongo) buildInsertQuery(ctx *loadContext, cl loadedCollection) ([]map[string]interface{}, error) { + // first pass, collecting all the fields + var fields []string + fieldPresence := make(map[string]bool) + for _, doc := range cl.documents { + for name := range doc { + if len(name) > 0 && name[0] == '$' { + continue + } + if _, ok := fieldPresence[name]; !ok { + fieldPresence[name] = true + fields = append(fields, name) + } + } + } + sort.Strings(fields) + + // second pass, collecting values + documents := make([]map[string]interface{}, len(cl.documents)) + for i, doc := range cl.documents { + valuesDoc := make(map[string]interface{}, len(doc)) + for _, name := range fields { + value, present := doc[name] + if !present { + continue + } + // resolve references + if stringValue, ok := value.(string); ok { + if len(stringValue) > 0 && stringValue[0] == '$' { + var err error + valuesDoc[name], err = f.resolveFieldReference(ctx.refsInserted, stringValue) + if err != nil { + return nil, err + } + continue + } + } + valuesDoc[name] = value + } + documents[i] = valuesDoc + } + + return documents, nil +} + +// resolveReference finds previously stored reference by its name +func (f *LoaderMongo) resolveReference(refs documentsDict, refName string) (document, error) { + target, ok := refs[refName] + if !ok { + return nil, fmt.Errorf("undefined reference %s", refName) + } + + // make a copy of referencing data to prevent spoiling the source + // by the way removing $-records from base row + targetCopy := make(document, len(target)) + for k, v := range target { + if len(k) == 0 || k[0] != '$' { + targetCopy[k] = v + } + } + + return targetCopy, nil +} + +// resolveFieldReference finds previously stored reference by name +// and return value of its field +func (f *LoaderMongo) resolveFieldReference(refs documentsDict, ref string) (interface{}, error) { + parts := strings.SplitN(ref, ".", 2) + if len(parts) < 2 || len(parts[0]) < 2 || len(parts[1]) < 1 { + return nil, fmt.Errorf("invalid reference %s, correct form is $refName.field", ref) + } + // remove leading $ + refName := parts[0][1:] + target, ok := refs[refName] + if !ok { + return nil, fmt.Errorf("undefined reference %s", refName) + } + value, ok := target[parts[1]] + if !ok { + return nil, fmt.Errorf("undefined reference field %s", parts[1]) + } + return value, nil +} + +// inArray checks whether the needle is present in haystack slice +func inArray(needle string, haystack []string) bool { + for _, e := range haystack { + if needle == e { + return true + } + } + + return false +} diff --git a/fixtures/mongo/mongo_test.go b/fixtures/mongo/mongo_test.go new file mode 100644 index 0000000..48c51df --- /dev/null +++ b/fixtures/mongo/mongo_test.go @@ -0,0 +1,227 @@ +package mongo + +import ( + "io/ioutil" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLoaderMongo_loadYml(t *testing.T) { + type args struct { + data []byte + ctx *loadContext + } + tests := []struct { + name string + args args + want loadContext + }{ + { + name: "basic", + args: args{ + data: loadTestData(t, "../testdata/mongo.yaml"), + ctx: &loadContext{ + refsDefinition: make(documentsDict), + refsInserted: make(documentsDict), + }, + }, + want: loadContext{ + refsDefinition: documentsDict{}, + refsInserted: documentsDict{}, + collections: []loadedCollection{ + { + name: collectionName{database: "public", name: "collection1"}, + documents: collection{ + { + "field1": "value1", + "field2": 1, + }, + { + "field1": "value2", + "field2": 2, + "field3": 2.569947773654566, + }, + }, + }, + { + name: collectionName{database: "public", name: "collection2"}, + documents: collection{ + { + "field4": false, + "field5": nil, + "field1": `"`, + }, + { + "field1": "'", + "field5": []interface{}{1, "2"}, + }, + }, + }, + }, + }, + }, + { + name: "database", + args: args{ + data: loadTestData(t, "../testdata/mongo_database.yaml"), + ctx: &loadContext{ + refsDefinition: make(documentsDict), + refsInserted: make(documentsDict), + }, + }, + want: loadContext{ + refsDefinition: documentsDict{}, + refsInserted: documentsDict{}, + collections: []loadedCollection{ + { + name: collectionName{database: "database1", name: "collection1"}, + documents: collection{ + { + "f1": "value1", + "f2": "value2", + }, + }, + }, + { + name: collectionName{database: "database2", name: "collection2"}, + documents: collection{ + { + "f1": "value3", + "f2": "value4", + }, + }, + }, + { + name: collectionName{database: "public", name: "collection3"}, + documents: collection{ + { + "f1": "value5", + "f2": "value6", + }, + }, + }, + }, + }, + }, + { + name: "extend", + args: args{ + data: loadTestData(t, "../testdata/mongo_extend.yaml"), + ctx: &loadContext{ + refsDefinition: documentsDict{}, + refsInserted: documentsDict{}, + }, + }, + want: loadContext{ + refsDefinition: documentsDict{ + "base_tmpl": { + "field1": "tplVal1", + }, + "ref3": { + "$extend": "base_tmpl", + "field1": "tplVal1", + "field2": "tplVal2", + }, + }, + refsInserted: documentsDict{}, + collections: []loadedCollection{ + { + name: collectionName{database: "public", name: "collection1"}, + documents: collection{ + { + "$name": "ref1", + "field1": "value1", + "field2": "value2", + }, + }, + }, + { + name: collectionName{database: "public", name: "collection2"}, + documents: collection{ + { + "$name": "ref2", + "$extend": "ref1", + "field1": "value1 overwritten", + }, + }, + }, + { + name: collectionName{database: "public", name: "collection3"}, + documents: collection{ + { + "$extend": "ref2", + }, + { + "$extend": "ref3", + }, + }, + }, + }, + }, + }, + { + name: "refs", + args: args{ + data: loadTestData(t, "../testdata/mongo_refs.yaml"), + ctx: &loadContext{ + refsDefinition: documentsDict{}, + refsInserted: documentsDict{}, + }, + }, + want: loadContext{ + refsDefinition: documentsDict{}, + refsInserted: documentsDict{}, + collections: []loadedCollection{ + { + name: collectionName{database: "public", name: "collection1"}, + documents: collection{ + { + "$name": "ref1", + "f1": "value1", + "f2": "value2", + }, + }, + }, + { + name: collectionName{database: "public", name: "collection2"}, + documents: collection{ + { + "$name": "ref2", + "f1": "$ref1.f2", + "f2": "$ref1.f1", + }, + }, + }, + { + name: collectionName{database: "public", name: "collection3"}, + documents: collection{ + { + "f1": "$ref1.f1", + "f2": "$ref2.f1", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &LoaderMongo{} + if err := f.loadYml(tt.args.data, tt.args.ctx); err != nil { + t.Errorf("LoaderMongo.loadYml() error = %v", err) + } + + require.Equal(t, tt.want, *tt.args.ctx) + }) + } +} + +func loadTestData(t *testing.T, path string) []byte { + yml, err := ioutil.ReadFile(path) + if err != nil { + t.Error("No " + path) + } + return yml +} diff --git a/fixtures/testdata/mongo.yaml b/fixtures/testdata/mongo.yaml new file mode 100644 index 0000000..6b7341c --- /dev/null +++ b/fixtures/testdata/mongo.yaml @@ -0,0 +1,15 @@ +collections: + collection1: + - field1: "value1" + field2: 1 + - field1: "value2" + field2: 2 + field3: 2.569947773654566 + collection2: + - field4: false + field5: null + field1: '"' + - field1: "'" + field5: + - 1 + - '2' \ No newline at end of file diff --git a/fixtures/testdata/mongo_database.yaml b/fixtures/testdata/mongo_database.yaml new file mode 100644 index 0000000..032774e --- /dev/null +++ b/fixtures/testdata/mongo_database.yaml @@ -0,0 +1,12 @@ +collections: + database1.collection1: + - f1: value1 + f2: value2 + + database2.collection2: + - f1: value3 + f2: value4 + + collection3: + - f1: value5 + f2: value6 \ No newline at end of file diff --git a/fixtures/testdata/mongo_extend.yaml b/fixtures/testdata/mongo_extend.yaml new file mode 100644 index 0000000..0050737 --- /dev/null +++ b/fixtures/testdata/mongo_extend.yaml @@ -0,0 +1,20 @@ +templates: + base_tmpl: + field1: tplVal1 + ref3: + $extend: base_tmpl + field2: tplVal2 +collections: + collection1: + - $name: ref1 + field1: value1 + field2: value2 + + collection2: + - $name: ref2 + $extend: ref1 + field1: value1 overwritten + + collection3: + - $extend: ref2 + - $extend: ref3 \ No newline at end of file diff --git a/fixtures/testdata/mongo_refs.yaml b/fixtures/testdata/mongo_refs.yaml new file mode 100644 index 0000000..ed5b65d --- /dev/null +++ b/fixtures/testdata/mongo_refs.yaml @@ -0,0 +1,14 @@ +collections: + collection1: + - $name: ref1 + f1: value1 + f2: value2 + + collection2: + - $name: ref2 + f1: $ref1.f2 + f2: $ref1.f1 + + collection3: + - f1: $ref1.f1 + f2: $ref2.f1 \ No newline at end of file diff --git a/go.mod b/go.mod index 2337eb2..476bce1 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/stretchr/testify v1.7.1 github.com/tidwall/gjson v1.17.0 github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 // indirect - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect + go.mongodb.org/mongo-driver v1.12.1 golang.org/x/sync v0.4.0 gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 980aa5d..666f286 100644 --- a/go.sum +++ b/go.sum @@ -34,9 +34,12 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -55,6 +58,8 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= @@ -70,6 +75,8 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -105,22 +112,34 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ= github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= +go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -133,12 +152,14 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -164,6 +185,8 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -171,13 +194,16 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go index 37aa543..6e3373b 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,12 @@ package main import ( + "context" "database/sql" "errors" "flag" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" "log" "net/url" "os" @@ -22,6 +25,7 @@ import ( "github.com/lamoda/gonkey/output/console_colored" "github.com/lamoda/gonkey/runner" aerospikeAdapter "github.com/lamoda/gonkey/storage/aerospike" + mongoAdapter "github.com/lamoda/gonkey/storage/mongo" "github.com/lamoda/gonkey/testloader/yaml_file" "github.com/lamoda/gonkey/variables" ) @@ -31,6 +35,7 @@ type config struct { TestsLocation string DbDsn string AerospikeHost string + MongoDsn string RedisURL string FixturesLocation string EnvFile string @@ -43,6 +48,7 @@ type config struct { type storages struct { db *sql.DB aerospike *aerospikeAdapter.Client + mongo *mongoAdapter.Client } func main() { @@ -91,19 +97,22 @@ func main() { func initStorages(cfg config) storages { db := initDB(cfg) aerospikeClient := initAerospike(cfg) + mongoClient := initMongo(cfg) return storages{ db: db, aerospike: aerospikeClient, + mongo: mongoClient, } } func initLoaders(storages storages, cfg config) fixtures.Loader { var fixturesLoader fixtures.Loader if cfg.FixturesLocation != "" { - if storages.db != nil || storages.aerospike != nil { + if storages.db != nil || storages.aerospike != nil || storages.mongo != nil { fixturesLoader = fixtures.NewLoader(&fixtures.Config{ DB: storages.db, Aerospike: storages.aerospike, + Mongo: storages.mongo, Location: cfg.FixturesLocation, Debug: cfg.Debug, DbType: fixtures.FetchDbType(cfg.DbType), @@ -183,6 +192,19 @@ func initAerospike(cfg config) *aerospikeAdapter.Client { return nil } +func initMongo(cfg config) *mongoAdapter.Client { + if cfg.MongoDsn != "" { + client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(cfg.MongoDsn)) + if err != nil { + log.Fatal("Couldn't connect to mongo: ", err) + } + + return mongoAdapter.New(client) + } + + return nil +} + func initDB(cfg config) *sql.DB { if cfg.DbDsn != "" { var err error @@ -203,6 +225,7 @@ func getConfig() config { flag.StringVar(&cfg.TestsLocation, "tests", "", "Path to tests file or directory") flag.StringVar(&cfg.DbDsn, "db_dsn", "", "DSN for the fixtures database (WARNING! Db tables will be truncated)") flag.StringVar(&cfg.AerospikeHost, "aerospike_host", "", "Aerospike host for fixtures in form of 'host:port/namespace' (WARNING! Aerospike sets will be truncated)") + flag.StringVar(&cfg.MongoDsn, "mongo_dsn", "", "Mongo DSN for the fixtures database (WARNING! Mongo collections will be truncated)") flag.StringVar(&cfg.RedisURL, "redis_url", "", "Redis server URL for fixture loading") flag.StringVar(&cfg.FixturesLocation, "fixtures", "", "Path to fixtures directory") flag.StringVar(&cfg.EnvFile, "env-file", "", "Path to env-file") @@ -213,7 +236,7 @@ func getConfig() config { &cfg.DbType, "db-type", fixtures.PostgresParam, - "Type of database (options: postgres, mysql, aerospike, redis)", + "Type of database (options: postgres, mysql, aerospike, redis, mongo)", ) flag.Parse() diff --git a/runner/runner_testing.go b/runner/runner_testing.go index 0697650..ae54624 100644 --- a/runner/runner_testing.go +++ b/runner/runner_testing.go @@ -12,6 +12,7 @@ import ( "github.com/aerospike/aerospike-client-go/v5" "github.com/joho/godotenv" + "go.mongodb.org/mongo-driver/mongo" "github.com/lamoda/gonkey/checker" "github.com/lamoda/gonkey/checker/response_body" @@ -24,6 +25,7 @@ import ( "github.com/lamoda/gonkey/output/allure_report" testingOutput "github.com/lamoda/gonkey/output/testing" aerospikeAdapter "github.com/lamoda/gonkey/storage/aerospike" + mongoAdapter "github.com/lamoda/gonkey/storage/mongo" "github.com/lamoda/gonkey/testloader/yaml_file" "github.com/lamoda/gonkey/variables" ) @@ -33,6 +35,10 @@ type Aerospike struct { Namespace string } +type Mongo struct { + *mongo.Client +} + type RunWithTestingParams struct { Server *httptest.Server TestsDir string @@ -40,6 +46,7 @@ type RunWithTestingParams struct { FixturesDir string DB *sql.DB Aerospike Aerospike + Mongo Mongo // If DB parameter present, used to recognize type of database, if not set, by default uses Postgres DbType fixtures.DbType EnvFilePath string @@ -74,11 +81,12 @@ func RunWithTesting(t *testing.T, params *RunWithTestingParams) { debug := os.Getenv("GONKEY_DEBUG") != "" var fixturesLoader fixtures.Loader - if params.DB != nil || params.Aerospike.Client != nil || params.FixtureLoader != nil { + if params.DB != nil || params.Aerospike.Client != nil || params.Mongo.Client != nil || params.FixtureLoader != nil { fixturesLoader = fixtures.NewLoader(&fixtures.Config{ Location: params.FixturesDir, DB: params.DB, Aerospike: aerospikeAdapter.New(params.Aerospike.Client, params.Aerospike.Namespace), + Mongo: mongoAdapter.New(params.Mongo.Client), Debug: debug, DbType: params.DbType, FixtureLoader: params.FixtureLoader, diff --git a/storage/mongo/mongo.go b/storage/mongo/mongo.go new file mode 100644 index 0000000..cfab9bc --- /dev/null +++ b/storage/mongo/mongo.go @@ -0,0 +1,67 @@ +package mongo + +import ( + "context" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +type Client struct { + *mongo.Client +} + +func New(client *mongo.Client) *Client { + return &Client{ + Client: client, + } +} + +func (c *Client) Truncate(database string, collection string) error { + cl := c.Client.Database(database).Collection(collection) + return cl.Drop(context.Background()) +} + +func (c *Client) InsertDocuments(database string, collection string, documents []map[string]interface{}) ([]map[string]interface{}, error) { + cl := c.Client.Database(database).Collection(collection) + insertResult, err := cl.InsertMany(context.Background(), sliceToInterfaces(documents)) + if err != nil { + return nil, err + } + + filter := bson.M{"_id": bson.M{"$in": insertResult.InsertedIDs}} + cursor, err := cl.Find(context.Background(), filter) + if err != nil { + return nil, err + } + + var result = make([]map[string]interface{}, len(insertResult.InsertedIDs)) + if err = cursor.All(context.Background(), &result); err != nil { + return nil, err + } + + result = sortedByInserted(insertResult.InsertedIDs, result) + return result, err +} + +func sortedByInserted(ids []interface{}, documents []map[string]interface{}) []map[string]interface{} { + idMap := make(map[interface{}]map[string]interface{}) + result := make([]map[string]interface{}, len(ids)) + + for _, doc := range documents { + idMap[doc["_id"]] = doc + } + + for i, id := range ids { + result[i] = idMap[id] + } + + return result +} + +func sliceToInterfaces(elements []map[string]interface{}) []interface{} { + var result []interface{} + for _, el := range elements { + result = append(result, el) + } + return result +}