Skip to content

Commit

Permalink
- Добавил MongoDB в README.md, README-ru.md
Browse files Browse the repository at this point in the history
- Поправил замечания из PR
- Исправлены баги с $ операторами
  • Loading branch information
Denis Chervinskiy committed Oct 24, 2023
1 parent 82cefee commit a652b22
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 40 deletions.
52 changes: 49 additions & 3 deletions README-ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Gonkey протестирует ваши сервисы, используя их

- работает с REST/JSON API
- проверка API сервиса на соответствие OpenAPI-спеке
- заполнение БД сервиса данными из фикстур (поддерживается PostgreSQL, MySQL, Aerospike, Redis)
- заполнение БД сервиса данными из фикстур (поддерживается PostgreSQL, MySQL, Aerospike, Redis, MongoDB)
- моки для имитации внешних сервисов
- можно подключить к проекту как библиотеку и запускать вместе с юнит-тестами
- запись результата тестов в виде отчета [Allure](http://allure.qatools.ru/)
Expand Down Expand Up @@ -35,6 +35,7 @@ Gonkey протестирует ваши сервисы, используя их
- [Выражения](#выражения)
- [Aerospike](#aerospike)
- [Redis](#redis)
- [MongoDB](#mongodb)
- [Моки](#моки)
- [Запуск моков при использовании gonkey как библиотеки](#запуск-моков-при-использовании-gonkey-как-библиотеки)
- [Описание моков в файле с тестом](#описание-моков-в-файле-с-тестом)
Expand All @@ -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` подробный вывод
Expand Down Expand Up @@ -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` не поддерживается.

## Моки

Чтобы для тестов имитировать ответы от внешних сервисов, применяются моки.
Expand Down
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down
117 changes: 106 additions & 11 deletions fixtures/mongo/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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 {
Expand Down

0 comments on commit a652b22

Please sign in to comment.