Skip to content

Commit a652b22

Browse files
author
Denis Chervinskiy
committed
- Добавил MongoDB в README.md, README-ru.md
- Поправил замечания из PR - Исправлены баги с $ операторами
1 parent 82cefee commit a652b22

File tree

9 files changed

+352
-40
lines changed

9 files changed

+352
-40
lines changed

README-ru.md

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Gonkey протестирует ваши сервисы, используя их
66

77
- работает с REST/JSON API
88
- проверка API сервиса на соответствие OpenAPI-спеке
9-
- заполнение БД сервиса данными из фикстур (поддерживается PostgreSQL, MySQL, Aerospike, Redis)
9+
- заполнение БД сервиса данными из фикстур (поддерживается PostgreSQL, MySQL, Aerospike, Redis, MongoDB)
1010
- моки для имитации внешних сервисов
1111
- можно подключить к проекту как библиотеку и запускать вместе с юнит-тестами
1212
- запись результата тестов в виде отчета [Allure](http://allure.qatools.ru/)
@@ -35,6 +35,7 @@ Gonkey протестирует ваши сервисы, используя их
3535
- [Выражения](#выражения)
3636
- [Aerospike](#aerospike)
3737
- [Redis](#redis)
38+
- [MongoDB](#mongodb)
3839
- [Моки](#моки)
3940
- [Запуск моков при использовании gonkey как библиотеки](#запуск-моков-при-использовании-gonkey-как-библиотеки)
4041
- [Описание моков в файле с тестом](#описание-моков-в-файле-с-тестом)
@@ -53,17 +54,18 @@ Gonkey протестирует ваши сервисы, используя их
5354

5455
## Использование консольной утилиты
5556

56-
Для тестирование сервиса, размещенного на удаленном хосте, используйте gonkey как консольную утилиту.
57+
Для тестирования сервиса, размещенного на удаленном хосте, используйте gonkey как консольную утилиту.
5758

5859
`./gonkey -host <...> -tests <...> [-spec <...>] [-db_dsn <...> -fixtures <...>] [-allure] [-v]`
5960

6061
- `-spec <...>` путь к файлу или URL со swagger-спецификацией сервиса
6162
- `-host <...>` хост:порт сервиса
6263
- `-tests <...>` файл или директория с тестами
63-
- `-db-type <...>` - тип базы данных. В данный момент поддерживается PostgreSQL, Aerospike, Redis.
64+
- `-db-type <...>` - тип базы данных. В данный момент поддерживается PostgreSQL, Aerospike, Redis, MongoDB.
6465
- `-db_dsn <...>` dsn для вашей тестовой SQL базы данных (бд будет очищена перед наполнением!), поддерживается только PostgreSQL
6566
- `-aerospike_host <...>` при использовании Aerospike - URL для подключения к нему в формате `host:port/namespace`
6667
- `-redis_url <...>` при использовании Redis - адрес для подключения к Redis, например `redis://user:password@localhost:6789/1?dial_timeout=1&db=1&read_timeout=6s&max_retries=2`
68+
- `-mongo_dsn <...>` при использовании MongoDB - URL для подключения в формате `mongodb://user:password@host:port`
6769
- `-fixtures <...>` директория с вашими фикстурами
6870
- `-allure` генерировать allure-отчет
6971
- `-v` подробный вывод
@@ -876,6 +878,50 @@ databases:
876878
value: value4
877879
```
878880

881+
### MongoDB
882+
883+
Для того, чтобы подключить MongoDB необходимо:
884+
- Для CLI-версии: использовать флаги `-db-type mongo` и `mongo_dsn { connectionString }`;
885+
- Для Package-версии: при конфигурации раннера установить `DbType: fixtures.Mongo` и пробросить mongo клиент `Mongo: {mongo client}`.
886+
887+
Формат файлов с фикстурами для Mongo:
888+
```yaml
889+
collections:
890+
collection1:
891+
- field1: "value1"
892+
field2: 1
893+
- field1: "value2"
894+
field2: 2
895+
field3: 2.569947773654566
896+
collection2:
897+
- field4: false
898+
field5: null
899+
field1: '"'
900+
- field1: "'"
901+
field5:
902+
- 1
903+
- '2'
904+
```
905+
906+
Если используются разные базы данных:
907+
908+
```yaml
909+
collections:
910+
database1.collection1:
911+
- f1: value1
912+
f2: value2
913+
914+
database2.collection2:
915+
- f1: value3
916+
f2: value4
917+
918+
collection3:
919+
- f1: value5
920+
f2: value6
921+
```
922+
923+
Оператор `eval` не поддерживается.
924+
879925
## Моки
880926

881927
Чтобы для тестов имитировать ответы от внешних сервисов, применяются моки.

README.md

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Capabilities:
88

99
- works with REST/JSON API
1010
- tests service API for compliance with OpenAPI-specs
11-
- seeds the DB with fixtures data (supports PostgreSQL, MySQL, Aerospike, Redis)
11+
- seeds the DB with fixtures data (supports PostgreSQL, MySQL, Aerospike, Redis, MongoDB)
1212
- provides mocks for external services
1313
- can be used as a library and ran together with unit-tests
1414
- stores the results as an [Allure](http://allure.qatools.ru/) report
@@ -37,6 +37,7 @@ Capabilities:
3737
- [Expressions](#expressions)
3838
- [Aerospike](#aerospike)
3939
- [Redis](#redis)
40+
- [MongoDB](#mongodb)
4041
- [Mocks](#mocks)
4142
- [Running mocks while using gonkey as a library](#running-mocks-while-using-gonkey-as-a-library)
4243
- [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.
6263
- `-spec <...>` path to a file or URL with the swagger-specs for the service
6364
- `-host <...>` service host:port
6465
- `-tests <...>` test file or directory
65-
- `-db-type <...>` - database type. PostgreSQL, Aerospike, Redis are currently supported.
66+
- `-db-type <...>` - database type. PostgreSQL, Aerospike, Redis, Mongo are currently supported.
6667
- `-aerospike_host <...>` when using Aerospike - connection URL in a form of `host:port/namespace`
68+
- `-mongo_dsn <...>` when using MongoDB - connection URL in a form of `mongodb://user:password@host:port`
6769
- `-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`
6870
- `-db_dsn <...>` DSN for the test DB (the DB will be cleared before seeding!), supports only PostgreSQL
6971
- `-fixtures <...>` fixtures directory
@@ -879,6 +881,49 @@ databases:
879881
value: value4
880882
```
881883

884+
### MongoDB
885+
886+
To connect to MongoDB, you need to:
887+
- For the CLI-version: use the flags -db-type mongo and mongo_dsn {connectionString};
888+
- For the Package-version: when configuring the runner, set DbType: fixtures.Mongo and pass the MongoDB client as Mongo: {mongo client}.
889+
890+
The format of fixture files for MongoDB:
891+
```yaml
892+
collections:
893+
collection1:
894+
- field1: "value1"
895+
field2: 1
896+
- field1: "value2"
897+
field2: 2
898+
field3: 2.569947773654566
899+
collection2:
900+
- field4: false
901+
field5: null
902+
field1: '"'
903+
- field1: "'"
904+
field5:
905+
- 1
906+
- '2'
907+
```
908+
909+
If you are using different databases:
910+
```yaml
911+
collections:
912+
database1.collection1:
913+
- f1: value1
914+
f2: value2
915+
916+
database2.collection2:
917+
- f1: value3
918+
f2: value4
919+
920+
collection3:
921+
- f1: value5
922+
f2: value6
923+
```
924+
925+
The `eval` operator is not supported.
926+
882927
## Mocks
883928

884929
In order to imitate responses from external services, use mocks.

fixtures/mongo/mongo.go

Lines changed: 106 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import (
77
"gopkg.in/yaml.v2"
88
"io/ioutil"
99
"os"
10+
"path/filepath"
11+
"sort"
1012
"strings"
1113
)
1214

1315
type mongoClient interface {
1416
Truncate(database string, collection string) error
15-
InsertDocument(database string, collection string, document map[string]interface{}) error
17+
InsertDocuments(database string, collection string, documents []map[string]interface{}) ([]map[string]interface{}, error)
1618
}
1719

1820
type LoaderMongo struct {
@@ -46,11 +48,10 @@ type collectionName struct {
4648
func newCollectionName(source string) collectionName {
4749
parts := strings.SplitN(source, ".", 2)
4850

49-
switch {
50-
case len(parts) == 1:
51+
if len(parts) == 1 {
5152
parts = append(parts, parts[0])
52-
fallthrough
53-
case parts[0] == "":
53+
parts[0] = "public"
54+
} else if parts[0] == "" {
5455
parts[0] = "public"
5556
}
5657

@@ -95,9 +96,9 @@ func (f *LoaderMongo) Load(names []string) error {
9596

9697
func (f *LoaderMongo) loadFile(name string, ctx *loadContext) error {
9798
candidates := []string{
98-
f.location + "/" + name,
99-
f.location + "/" + name + ".yml",
100-
f.location + "/" + name + ".yaml",
99+
filepath.Join(f.location, name),
100+
filepath.Join(f.location, name, ".yml"),
101+
filepath.Join(f.location, name, ".yaml"),
101102
}
102103

103104
var err error
@@ -274,15 +275,89 @@ func (f *LoaderMongo) loadCollection(ctx *loadContext, cl loadedCollection) erro
274275
}
275276
}
276277

277-
for _, doc := range cl.documents {
278-
if err := f.client.InsertDocument(cl.name.database, cl.name.name, doc); err != nil {
279-
return err
278+
query, err := f.buildInsertQuery(ctx, cl)
279+
if err != nil {
280+
return err
281+
}
282+
insertedDocs, err := f.client.InsertDocuments(cl.name.database, cl.name.name, query)
283+
if err != nil {
284+
return err
285+
}
286+
287+
// reading results
288+
// here I assume that returning rows go in the same
289+
// order as values were passed to INSERT statement
290+
for i, doc := range cl.documents {
291+
if name, ok := doc["$name"]; ok {
292+
name := name.(string)
293+
if _, ok := ctx.refsDefinition[name]; ok {
294+
return fmt.Errorf("duplicating ref name %s", name)
295+
}
296+
// add to references
297+
ctx.refsDefinition[name] = doc
298+
if f.debug {
299+
docJson, _ := json.Marshal(doc)
300+
fmt.Printf("Populating ref %s as %s from doc definition\n", name, string(docJson))
301+
}
302+
values := insertedDocs[i]
303+
ctx.refsInserted[name] = values
304+
if f.debug {
305+
valuesJson, _ := json.Marshal(values)
306+
fmt.Printf("Populating ref %s as %s from inserted values\n", name, string(valuesJson))
307+
}
280308
}
281309
}
282310

283311
return nil
284312
}
285313

314+
// buildInsertQuery builds query for data insertion
315+
// based on values read from yaml
316+
func (f *LoaderMongo) buildInsertQuery(ctx *loadContext, cl loadedCollection) ([]map[string]interface{}, error) {
317+
// first pass, collecting all the fields
318+
var fields []string
319+
fieldPresence := make(map[string]bool)
320+
for _, doc := range cl.documents {
321+
for name := range doc {
322+
if len(name) > 0 && name[0] == '$' {
323+
continue
324+
}
325+
if _, ok := fieldPresence[name]; !ok {
326+
fieldPresence[name] = true
327+
fields = append(fields, name)
328+
}
329+
}
330+
}
331+
sort.Strings(fields)
332+
333+
// second pass, collecting values
334+
documents := make([]map[string]interface{}, len(cl.documents))
335+
for i, doc := range cl.documents {
336+
valuesDoc := make(map[string]interface{}, len(doc))
337+
for _, name := range fields {
338+
value, present := doc[name]
339+
if !present {
340+
continue
341+
}
342+
// resolve references
343+
if stringValue, ok := value.(string); ok {
344+
if len(stringValue) > 0 && stringValue[0] == '$' {
345+
var err error
346+
valuesDoc[name], err = f.resolveFieldReference(ctx.refsInserted, stringValue)
347+
if err != nil {
348+
return nil, err
349+
}
350+
continue
351+
}
352+
}
353+
valuesDoc[name] = value
354+
}
355+
documents[i] = valuesDoc
356+
}
357+
358+
return documents, nil
359+
}
360+
286361
// resolveReference finds previously stored reference by its name
287362
func (f *LoaderMongo) resolveReference(refs documentsDict, refName string) (document, error) {
288363
target, ok := refs[refName]
@@ -302,6 +377,26 @@ func (f *LoaderMongo) resolveReference(refs documentsDict, refName string) (docu
302377
return targetCopy, nil
303378
}
304379

380+
// resolveFieldReference finds previously stored reference by name
381+
// and return value of its field
382+
func (f *LoaderMongo) resolveFieldReference(refs documentsDict, ref string) (interface{}, error) {
383+
parts := strings.SplitN(ref, ".", 2)
384+
if len(parts) < 2 || len(parts[0]) < 2 || len(parts[1]) < 1 {
385+
return nil, fmt.Errorf("invalid reference %s, correct form is $refName.field", ref)
386+
}
387+
// remove leading $
388+
refName := parts[0][1:]
389+
target, ok := refs[refName]
390+
if !ok {
391+
return nil, fmt.Errorf("undefined reference %s", refName)
392+
}
393+
value, ok := target[parts[1]]
394+
if !ok {
395+
return nil, fmt.Errorf("undefined reference field %s", parts[1])
396+
}
397+
return value, nil
398+
}
399+
305400
// inArray checks whether the needle is present in haystack slice
306401
func inArray(needle string, haystack []string) bool {
307402
for _, e := range haystack {

0 commit comments

Comments
 (0)