From 8bf8089dd0b9d8095a760465c40b3052bc7275fa Mon Sep 17 00:00:00 2001 From: Denis Chervinskiy Date: Wed, 25 Oct 2023 01:41:59 +0300 Subject: [PATCH] =?UTF-8?q?-=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20MongoDB=20=D0=B2=20README.md,=20README-ru.md=20-=20=D0=9F?= =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=B7=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=B7=20PR=20-=20?= =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=B3=D0=B8=20=D1=81=20$=20=D0=BE=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-ru.md | 52 ++++++++++++- README.md | 49 +++++++++++- fixtures/mongo/mongo.go | 117 +++++++++++++++++++++++++--- fixtures/mongo/mongo_test.go | 76 +++++++++++++++--- fixtures/testdata/mongo.yaml | 2 +- fixtures/testdata/mongo_extend.yaml | 22 ++++-- fixtures/testdata/mongo_refs.yaml | 14 ++++ go.sum | 14 +++- storage/mongo/mongo.go | 46 ++++++++++- 9 files changed, 352 insertions(+), 40 deletions(-) create mode 100644 fixtures/testdata/mongo_refs.yaml 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/mongo/mongo.go b/fixtures/mongo/mongo.go index d7791cc..1d72145 100644 --- a/fixtures/mongo/mongo.go +++ b/fixtures/mongo/mongo.go @@ -7,12 +7,14 @@ import ( "gopkg.in/yaml.v2" "io/ioutil" "os" + "path/filepath" + "sort" "strings" ) type mongoClient interface { Truncate(database string, collection string) error - InsertDocument(database string, collection string, document map[string]interface{}) error + InsertDocuments(database string, collection string, documents []map[string]interface{}) ([]map[string]interface{}, error) } type LoaderMongo struct { @@ -46,11 +48,10 @@ type collectionName struct { func newCollectionName(source string) collectionName { parts := strings.SplitN(source, ".", 2) - switch { - case len(parts) == 1: + if len(parts) == 1 { parts = append(parts, parts[0]) - fallthrough - case parts[0] == "": + parts[0] = "public" + } else if parts[0] == "" { parts[0] = "public" } @@ -95,9 +96,9 @@ func (f *LoaderMongo) Load(names []string) error { func (f *LoaderMongo) loadFile(name string, ctx *loadContext) error { candidates := []string{ - f.location + "/" + name, - f.location + "/" + name + ".yml", - f.location + "/" + name + ".yaml", + filepath.Join(f.location, name), + filepath.Join(f.location, name, ".yml"), + filepath.Join(f.location, name, ".yaml"), } var err error @@ -274,15 +275,89 @@ func (f *LoaderMongo) loadCollection(ctx *loadContext, cl loadedCollection) erro } } - for _, doc := range cl.documents { - if err := f.client.InsertDocument(cl.name.database, cl.name.name, doc); err != nil { - return err + 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] @@ -302,6 +377,26 @@ func (f *LoaderMongo) resolveReference(refs documentsDict, refName string) (docu 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 { diff --git a/fixtures/mongo/mongo_test.go b/fixtures/mongo/mongo_test.go index 2792d5e..48c51df 100644 --- a/fixtures/mongo/mongo_test.go +++ b/fixtures/mongo/mongo_test.go @@ -40,7 +40,7 @@ func TestLoaderMongo_loadYml(t *testing.T) { { "field1": "value2", "field2": 2, - "field3": 2.569947773654566473, + "field3": 2.569947773654566, }, }, }, @@ -116,12 +116,12 @@ func TestLoaderMongo_loadYml(t *testing.T) { want: loadContext{ refsDefinition: documentsDict{ "base_tmpl": { - "field1": "value1", + "field1": "tplVal1", }, - "extended_tmpl": { + "ref3": { "$extend": "base_tmpl", - "field1": "value1", - "field2": "value2", + "field1": "tplVal1", + "field2": "tplVal2", }, }, refsInserted: documentsDict{}, @@ -130,8 +130,9 @@ func TestLoaderMongo_loadYml(t *testing.T) { name: collectionName{database: "public", name: "collection1"}, documents: collection{ { - "$extend": "base_tmpl", - "field1": "overwritten", + "$name": "ref1", + "field1": "value1", + "field2": "value2", }, }, }, @@ -139,8 +140,65 @@ func TestLoaderMongo_loadYml(t *testing.T) { name: collectionName{database: "public", name: "collection2"}, documents: collection{ { - "$extend": "extended_tmpl", - "field2": "overwritten", + "$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", }, }, }, diff --git a/fixtures/testdata/mongo.yaml b/fixtures/testdata/mongo.yaml index 201dda6..6b7341c 100644 --- a/fixtures/testdata/mongo.yaml +++ b/fixtures/testdata/mongo.yaml @@ -4,7 +4,7 @@ collections: field2: 1 - field1: "value2" field2: 2 - field3: 2.569947773654566473 + field3: 2.569947773654566 collection2: - field4: false field5: null diff --git a/fixtures/testdata/mongo_extend.yaml b/fixtures/testdata/mongo_extend.yaml index 8482bd1..0050737 100644 --- a/fixtures/testdata/mongo_extend.yaml +++ b/fixtures/testdata/mongo_extend.yaml @@ -1,14 +1,20 @@ templates: base_tmpl: - field1: value1 - extended_tmpl: + field1: tplVal1 + ref3: $extend: base_tmpl - field2: value2 - + field2: tplVal2 collections: collection1: - - $extend: base_tmpl - field1: overwritten + - $name: ref1 + field1: value1 + field2: value2 + collection2: - - $extend: extended_tmpl - field2: overwritten \ No newline at end of file + - $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.sum b/go.sum index 0889f98..36fde03 100644 --- a/go.sum +++ b/go.sum @@ -134,12 +134,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U 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-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 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/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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= @@ -153,8 +156,9 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx 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/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/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 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= @@ -186,15 +190,18 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc 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/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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= @@ -203,6 +210,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f 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/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 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/storage/mongo/mongo.go b/storage/mongo/mongo.go index 1b17e21..cfab9bc 100644 --- a/storage/mongo/mongo.go +++ b/storage/mongo/mongo.go @@ -2,6 +2,7 @@ package mongo import ( "context" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) @@ -20,8 +21,47 @@ func (c *Client) Truncate(database string, collection string) error { return cl.Drop(context.Background()) } -func (c *Client) InsertDocument(database string, collection string, document map[string]interface{}) error { +func (c *Client) InsertDocuments(database string, collection string, documents []map[string]interface{}) ([]map[string]interface{}, error) { cl := c.Client.Database(database).Collection(collection) - _, err := cl.InsertOne(context.Background(), document) - return err + 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 }