diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 14f078a..92a8add 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,4 +1,4 @@ -name: godog-example-setup +name: godog-http-api on: push: diff --git a/README.MD b/README.MD index eb9d1bc..4f14dc2 100644 --- a/README.MD +++ b/README.MD @@ -12,9 +12,9 @@ This project **cuts initial time** & allows bootstrap e2e test framework with ** utility methods** in just few steps. Just grab it and write tests right away! Benefits: -* [35+ well-documented, coupled in logical groups steps](https://github.com/pawelWritesCode/godog-http-api/wiki/Steps) useful for testing HTTP(s) API, +* [40+ well-documented, coupled in logical groups steps](https://github.com/pawelWritesCode/godog-http-api/wiki/Steps) useful for testing HTTP(s) API, * support for using templated values as in [text/template](https://pkg.go.dev/text/template) package, -* support for querying nodes with different path engines ([oliveagle](https://github.com/oliveagle/jsonpath), [qjson](https://github.com/pawelWritesCode/qjson) - JSON, [go-yaml](https://github.com/goccy/go-yaml) - YAML, [antchfx](https://github.com/antchfx/xmlquery) - XML), +* support for querying nodes with different path engines ([oliveagle](https://github.com/oliveagle/jsonpath), [gjson](https://github.com/tidwall/gjson) - JSON, [go-yaml](https://github.com/goccy/go-yaml) - YAML, [antchfx](https://github.com/antchfx/xmlquery) - XML), * support for sending _multipart/form-data_ forms with file in it, * developed with debugging in mind, * customisable through ability to [replace](https://github.com/pawelWritesCode/godog-http-api/blob/main/main_test.go#L53) utility services with your own implementations, @@ -41,7 +41,7 @@ Feature: Adding new user Given I generate a random word having from "5" to "10" of "ASCII" characters and save it as "RANDOM_FIRST_NAME" Given I generate a random word having from "3" to "7" of "UNICODE" characters and save it as "RANDOM_LAST_NAME" Given I generate a random sentence having from "3" to "4" of "english" words and save it as "RANDOM_DESCRIPTION" - Given I generate a random "int" in the range from "18" to "48" and save it as "RANDOM_AGE" + Given I generate a random "int" in the range from "18" to "20" and save it as "RANDOM_AGE" Given I generate current time and travel "backward" "240h" in time and save it as "MEET_DATE" Given I save "application/json" as "CONTENT_TYPE_JSON" @@ -77,21 +77,19 @@ Feature: Adding new user But the response body should have format "JSON" And time between last request and response should be less than or equal to "2s" - # uncommenting next line will print last HTTP(s) response body to console + # uncommenting next line will print data to console # Given I print last response body +# Given I print cache data # This waiting is unnecessary, just added for demonstration And I wait "2ms" #--------------------------------------------------------------------------------------------------- # We validate response body with schema from assets/test_server/doc/schema/user/user.json - # step argument may be: relative (see .env variable GODOG_JSON_SCHEMA_DIR) + # step argument may be: relative (see .env variable GODOG_JSON_SCHEMA_DIR), full OS path, URL or raw schema definition And the response body should be valid according to schema "user/user.json" - # or full OS path And the response body should be valid according to schema "{{.CWD}}/assets/test_server/doc/schema/user/user.json" - # or URL pointing at schema And the response body should be valid according to schema "https://raw.githubusercontent.com/pawelWritesCode/godog-http-api/main/assets/test_server/doc/schema/user/user.json" - # or raw schema definition passed in Docstring And the response body should be valid according to schema: """ { @@ -110,17 +108,28 @@ Feature: Adding new user "type": "string" } """ - # here is used qjson "json-path" syntax to find JSON node + # here are used two different json-path engines (gjson & oliveagle) to find JSON nodes And the "JSON" node "firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" - # here is used oliveagle "json-path" syntax to find JSON node And the "JSON" node "$.lastName" should be "string" of value "doe-{{.RANDOM_LAST_NAME}}" + + # here we look for substrings + And the "JSON" node "$.lastName" should not contain sub string "smith" + But the "JSON" node "$.lastName" should contain sub string "doe" + # here is used regExp acceptable by standard go package "regExp" And the "JSON" node "lastName" should not match regExp "smith-.*" But the "JSON" node "lastName" should match regExp "doe-.*" + + # assertion may be based on one of JSON data types: array, boolean, null, number, object And the "JSON" node "age" should not be "string" + But the "JSON" node "$.age" should be "number" + And the "JSON" node "$.age" should be "number" and contain one of values "18, 19, 20" + + # or on one of Go-like data types: bool, float, int, map, slice, string But the "JSON" node "$.age" should be "int" And the "JSON" node "age" should be "int" of value "{{.RANDOM_AGE}}" And the "JSON" node "description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" + # here date is formatted according to one of available formats from standard go package "time" And the "JSON" node "friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" ``` diff --git a/defs/scenario.go b/defs/scenario.go index 130f2de..bf90e6a 100644 --- a/defs/scenario.go +++ b/defs/scenario.go @@ -12,6 +12,7 @@ import ( "github.com/pawelWritesCode/gdutils/pkg/format" "github.com/pawelWritesCode/gdutils/pkg/stringutils" "github.com/pawelWritesCode/gdutils/pkg/timeutils" + "github.com/pawelWritesCode/gdutils/pkg/types" ) // Scenario is entity that contains utility services and holds methods used behind godog steps. @@ -36,8 +37,12 @@ func (s *Scenario) IGenerateARandomRunesOfLengthWithCharactersAndSaveItAs(from, generateWordFunc = s.APIContext.GeneratorRandomRunes(stringutils.CharsetEnglish) case "russian": generateWordFunc = s.APIContext.GeneratorRandomRunes(stringutils.CharsetRussian) + case "japanese": + generateWordFunc = s.APIContext.GeneratorRandomRunes(stringutils.CharsetJapanese) + case "emoji": + generateWordFunc = s.APIContext.GeneratorRandomRunes(stringutils.CharsetEmoji) default: - return fmt.Errorf("unknown charset '%s', available: ascii, unicode, polish, english, russian", charset) + return fmt.Errorf("unknown charset '%s', available: ascii, unicode, polish, english, russian, japanese, emoji", charset) } return generateWordFunc(from, to, cacheKey) @@ -72,8 +77,12 @@ func (s *Scenario) IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(minW generateSentenceFunc = s.APIContext.GeneratorRandomSentence(stringutils.CharsetEnglish, minWordLength, maxWordLength) case "russian": generateSentenceFunc = s.APIContext.GeneratorRandomSentence(stringutils.CharsetRussian, minWordLength, maxWordLength) + case "japanese": + generateSentenceFunc = s.APIContext.GeneratorRandomSentence(stringutils.CharsetJapanese, minWordLength, maxWordLength) + case "emoji": + generateSentenceFunc = s.APIContext.GeneratorRandomSentence(stringutils.CharsetEmoji, minWordLength, maxWordLength) default: - return fmt.Errorf("unknown charset '%s', available: ascii, unicode, polish, english, russian", charset) + return fmt.Errorf("unknown charset '%s', available: ascii, unicode, polish, english, russian, japanese, emoji", charset) } return generateSentenceFunc(from, to, cacheKey) @@ -180,11 +189,28 @@ func (s *Scenario) TheResponseShouldOrShouldNotHaveNode(dataFormat, not, exprTem return s.APIContext.AssertNodeExists(format.DataFormat(dataFormat), exprTemplate) } -// TheNodeShouldBeOfValue compares json node value from expression to expected by user dataValue of given by user dataType +// TheNodeShouldBeOfValue compares node value from expression to expected by user dataValue of given by user dataType // Available data types are listed in switch section in each case directive. // expr should be valid according to injected PathFinder for provided dataFormat. func (s *Scenario) TheNodeShouldBeOfValue(dataFormat, exprTemplate, dataType, dataValue string) error { - return s.APIContext.AssertNodeIsTypeAndValue(format.DataFormat(dataFormat), exprTemplate, dataType, dataValue) + return s.APIContext.AssertNodeIsTypeAndValue(format.DataFormat(dataFormat), exprTemplate, types.DataType(dataType), dataValue) +} + +// TheNodeShouldBeOfValues compares node value from expression to expected by user one of values of given by user dataType +// Available data types are listed in switch section in each case directive. +// expr should be valid according to injected PathFinder for provided dataFormat. +func (s *Scenario) TheNodeShouldBeOfValues(dataFormat, exprTemplate, dataType, valuesTemplates string) error { + return s.APIContext.AssertNodeIsTypeAndHasOneOfValues(format.DataFormat(dataFormat), exprTemplate, types.DataType(dataType), valuesTemplates) +} + +// TheNodeShouldOrShouldNotContainSubString checks whether value of last HTTP response node, obtained using exprTemplate +// is string type and contains/doesn't contain given substring +func (s *Scenario) TheNodeShouldOrShouldNotContainSubString(dataFormat, exprTemplate, not, subTemplate string) error { + if len(not) > 0 { + return s.APIContext.AssertNodeNotContainsSubString(format.DataFormat(dataFormat), exprTemplate, subTemplate) + } + + return s.APIContext.AssertNodeContainsSubString(format.DataFormat(dataFormat), exprTemplate, subTemplate) } // TheNodeShouldOrShouldNotBeSliceOfLength checks whether given key is slice and has/hasn't given length @@ -202,10 +228,10 @@ func (s *Scenario) TheNodeShouldOrShouldNotBeSliceOfLength(dataFormat, exprTempl // expr should be valid according to injected PathResolver func (s *Scenario) TheNodeShouldOrShouldNotBe(dataFormat, exprTemplate, not, goType string) error { if len(not) > 0 { - return s.APIContext.AssertNodeIsNotType(format.DataFormat(dataFormat), exprTemplate, goType) + return s.APIContext.AssertNodeIsNotType(format.DataFormat(dataFormat), exprTemplate, types.DataType(goType)) } - return s.APIContext.AssertNodeIsType(format.DataFormat(dataFormat), exprTemplate, goType) + return s.APIContext.AssertNodeIsType(format.DataFormat(dataFormat), exprTemplate, types.DataType(goType)) } // TheResponseShouldHaveNodes checks whether last request body has keys defined in string separated by comma @@ -277,6 +303,16 @@ func (s *Scenario) TheResponseShouldHaveCookieOfValue(name, valueTemplate string return s.APIContext.AssertResponseCookieValueIs(name, valueTemplate) } +// TheResponseCookieShouldOrShouldNotMatchRegExp checks whether last HTTP(s) response has cookie of given name and value +// matches/doesn't match provided regExp. +func (s *Scenario) TheResponseCookieShouldOrShouldNotMatchRegExp(name, not, regExpTemplate string) error { + if len(not) > 0 { + return s.APIContext.AssertResponseCookieValueNotMatchesRegExp(name, regExpTemplate) + } + + return s.APIContext.AssertResponseCookieValueMatchesRegExp(name, regExpTemplate) +} + // IValidateNodeWithSchemaReference validates last response body node against schema as provided in reference func (s *Scenario) IValidateNodeWithSchemaReference(dataFormat, exprTemplate, referenceTemplate string) error { return s.APIContext.AssertNodeMatchesSchemaByReference(format.DataFormat(dataFormat), exprTemplate, referenceTemplate) @@ -307,6 +343,13 @@ func (s *Scenario) IPrintLastResponseBody() error { return s.APIContext.DebugPrintResponseBody() } +// IPrintCacheData prints all current scenario cache data. +func (s *Scenario) IPrintCacheData() error { + fmt.Printf("%#v", s.APIContext.Cache.All()) + + return nil +} + /* IWait waits for provided time interval amount of time timeInterval should be string valid for time.ParseDuration func, diff --git a/features/test_server/json/create.feature b/features/test_server/json/create.feature index 1972dfe..61bc3d8 100644 --- a/features/test_server/json/create.feature +++ b/features/test_server/json/create.feature @@ -14,7 +14,7 @@ Feature: Adding new user Given I generate a random word having from "5" to "10" of "ASCII" characters and save it as "RANDOM_FIRST_NAME" Given I generate a random word having from "3" to "7" of "UNICODE" characters and save it as "RANDOM_LAST_NAME" Given I generate a random sentence having from "3" to "4" of "english" words and save it as "RANDOM_DESCRIPTION" - Given I generate a random "int" in the range from "18" to "48" and save it as "RANDOM_AGE" + Given I generate a random "int" in the range from "18" to "20" and save it as "RANDOM_AGE" Given I generate current time and travel "backward" "240h" in time and save it as "MEET_DATE" Given I save "application/json" as "CONTENT_TYPE_JSON" @@ -50,21 +50,19 @@ Feature: Adding new user But the response body should have format "JSON" And time between last request and response should be less than or equal to "2s" - # uncommenting next line will print last HTTP(s) response body to console + # uncommenting next line will print data to console # Given I print last response body +# Given I print cache data # This waiting is unnecessary, just added for demonstration And I wait "2ms" #--------------------------------------------------------------------------------------------------- # We validate response body with schema from assets/test_server/doc/schema/user/user.json - # step argument may be: relative (see .env variable GODOG_JSON_SCHEMA_DIR) + # step argument may be: relative (see .env variable GODOG_JSON_SCHEMA_DIR), full OS path, URL or raw schema definition And the response body should be valid according to schema "user/user.json" - # or full OS path And the response body should be valid according to schema "{{.CWD}}/assets/test_server/doc/schema/user/user.json" - # or URL pointing at schema And the response body should be valid according to schema "https://raw.githubusercontent.com/pawelWritesCode/godog-http-api/main/assets/test_server/doc/schema/user/user.json" - # or raw schema definition passed in Docstring And the response body should be valid according to schema: """ { @@ -83,17 +81,28 @@ Feature: Adding new user "type": "string" } """ - # here is used qjson "json-path" syntax to find JSON node + # here are used two different json-path engines (gjson & oliveagle) to find JSON nodes And the "JSON" node "firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" - # here is used oliveagle "json-path" syntax to find JSON node And the "JSON" node "$.lastName" should be "string" of value "doe-{{.RANDOM_LAST_NAME}}" + + # here we look for substrings + And the "JSON" node "$.lastName" should not contain sub string "smith" + But the "JSON" node "$.lastName" should contain sub string "doe" + # here is used regExp acceptable by standard go package "regExp" And the "JSON" node "lastName" should not match regExp "smith-.*" But the "JSON" node "lastName" should match regExp "doe-.*" + + # assertion may be based on one of JSON data types: array, boolean, null, number, object And the "JSON" node "age" should not be "string" + But the "JSON" node "$.age" should be "number" + And the "JSON" node "$.age" should be "number" and contain one of values "18, 19, 20" + + # or on one of Go-like data types: bool, float, int, map, slice, string But the "JSON" node "$.age" should be "int" And the "JSON" node "age" should be "int" of value "{{.RANDOM_AGE}}" And the "JSON" node "description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" + # here date is formatted according to one of available formats from standard go package "time" And the "JSON" node "friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" diff --git a/features/test_server/json/get_many.feature b/features/test_server/json/get_many.feature index 518bf89..2fa3013 100644 --- a/features/test_server/json/get_many.feature +++ b/features/test_server/json/get_many.feature @@ -83,6 +83,7 @@ Feature: Fetching many users. And the response should have header "Content-Type" of value "application/json; charset=UTF-8" And the response body should have format "JSON" # here we only check only node type, not its exact value - And the "JSON" node "root" should be "slice" - But the "JSON" node "root" should not be slice of length "0" - And the "JSON" node "root" should not be "nil" \ No newline at end of file + And the "JSON" node "@this" should be "slice" + But the "JSON" node "@this" should not be slice of length "0" + And the "JSON" node "@this" should not be "nil" + And the "JSON" node "@this" should not be "null" \ No newline at end of file diff --git a/features/test_server/json/get_one.feature b/features/test_server/json/get_one.feature index 5567a9e..29a18c8 100644 --- a/features/test_server/json/get_one.feature +++ b/features/test_server/json/get_one.feature @@ -55,8 +55,8 @@ Feature: Fetching single user. And the response body should be valid according to schema "user/user.json" And the "JSON" node "firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" And the "JSON" node "lastName" should be "string" of value "{{.RANDOM_LAST_NAME}}" - And the "JSON" node "age" should be "int" of value "{{.RANDOM_AGE}}" - And the "JSON" node "id" should be "int" of value "{{.USER_ID}}" + And the "JSON" node "age" should be "number" of value "{{.RANDOM_AGE}}" + And the "JSON" node "id" should be "number" of value "{{.USER_ID}}" And the "JSON" node "description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" And the "JSON" node "friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" diff --git a/features/test_server/json/replace.feature b/features/test_server/json/replace.feature index b39df4b..60c0b01 100644 --- a/features/test_server/json/replace.feature +++ b/features/test_server/json/replace.feature @@ -55,8 +55,8 @@ Feature: Replacing single user account. And the response body should be valid according to schema "user/user.json" And the "JSON" node "firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" And the "JSON" node "lastName" should be "string" of value "{{.RANDOM_LAST_NAME}}" - And the "JSON" node "age" should be "int" of value "{{.RANDOM_AGE}}" - And the "JSON" node "id" should be "int" of value "{{.USER_ID}}" + And the "JSON" node "age" should be "number" of value "{{.RANDOM_AGE}}" + And the "JSON" node "id" should be "number" of value "{{.USER_ID}}" And the "JSON" node "description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" And the "JSON" node "friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" @@ -107,8 +107,8 @@ Feature: Replacing single user account. And the response body should be valid according to schema "user/user.json" And the "JSON" node "firstName" should be "string" of value "{{.NEW_USER_RANDOM_FIRST_NAME}}" And the "JSON" node "lastName" should be "string" of value "{{.NEW_USER_RANDOM_LAST_NAME}}" - And the "JSON" node "age" should be "int" of value "{{.NEW_USER_RANDOM_AGE}}" - And the "JSON" node "id" should be "int" of value "{{.USER_ID}}" + And the "JSON" node "age" should be "number" of value "{{.NEW_USER_RANDOM_AGE}}" + And the "JSON" node "id" should be "number" of value "{{.USER_ID}}" And the "JSON" node "description" should be "string" of value "{{.NEW_USER_RANDOM_DESCRIPTION}}" And the "JSON" node "friendSince" should be "string" of value "{{.NEW_USER_MEET_DATE.Format `2006-01-02T15:04:05Z`}}" @@ -185,8 +185,8 @@ Feature: Replacing single user account. And the response body should be valid according to schema "user/user.json" And the "JSON" node "firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" And the "JSON" node "lastName" should be "string" of value "{{.RANDOM_LAST_NAME}}" - And the "JSON" node "age" should be "int" of value "{{.RANDOM_AGE}}" - And the "JSON" node "id" should be "int" of value "{{.USER_ID}}" + And the "JSON" node "age" should be "number" of value "{{.RANDOM_AGE}}" + And the "JSON" node "id" should be "number" of value "{{.USER_ID}}" And the "JSON" node "description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" And the "JSON" node "friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" diff --git a/features/test_server/xml/create.feature b/features/test_server/xml/create.feature index ea4e699..fc12e8e 100644 --- a/features/test_server/xml/create.feature +++ b/features/test_server/xml/create.feature @@ -50,7 +50,7 @@ Feature: Adding new user And the "XML" node "//firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" And the "XML" node "//lastName" should be "string" of value "doe-{{.RANDOM_LAST_NAME}}" And the "XML" node "//lastName" should match regExp "doe-.*" - And the "XML" node "//age" should be "int" of value "{{.RANDOM_AGE}}" + And the "XML" node "//age" should be "integer" of value "{{.RANDOM_AGE}}" And the "XML" node "//description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" And the "XML" node "//friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" diff --git a/features/test_server/xml/get_one.feature b/features/test_server/xml/get_one.feature index 222af77..d13c401 100644 --- a/features/test_server/xml/get_one.feature +++ b/features/test_server/xml/get_one.feature @@ -54,7 +54,7 @@ Feature: Fetching single user. And the "XML" node "//firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" And the "XML" node "//lastName" should be "string" of value "{{.RANDOM_LAST_NAME}}" And the "XML" node "//age" should be "int" of value "{{.RANDOM_AGE}}" - And the "XML" node "//id" should be "int" of value "{{.USER_ID}}" + And the "XML" node "//id" should be "integer" of value "{{.USER_ID}}" And the "XML" node "//description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" And the "XML" node "//friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" diff --git a/features/test_server/xml/replace.feature b/features/test_server/xml/replace.feature index d5a1796..8cf2356 100644 --- a/features/test_server/xml/replace.feature +++ b/features/test_server/xml/replace.feature @@ -104,8 +104,8 @@ Feature: Replacing single user account. And the response body should have format "XML" And the "XML" node "//firstName" should be "string" of value "{{.NEW_USER_RANDOM_FIRST_NAME}}" And the "XML" node "//lastName" should be "string" of value "{{.NEW_USER_RANDOM_LAST_NAME}}" - And the "XML" node "//age" should be "int" of value "{{.NEW_USER_RANDOM_AGE}}" - And the "XML" node "//id" should be "int" of value "{{.USER_ID}}" + And the "XML" node "//age" should be "integer" of value "{{.NEW_USER_RANDOM_AGE}}" + And the "XML" node "//id" should be "integer" of value "{{.USER_ID}}" And the "XML" node "//description" should be "string" of value "{{.NEW_USER_RANDOM_DESCRIPTION}}" And the "XML" node "//friendSince" should be "string" of value "{{.NEW_USER_MEET_DATE.Format `2006-01-02T15:04:05Z`}}" @@ -178,8 +178,8 @@ Feature: Replacing single user account. And the response body should have format "XML" And the "XML" node "//firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" And the "XML" node "//lastName" should be "string" of value "{{.RANDOM_LAST_NAME}}" - And the "XML" node "//age" should be "int" of value "{{.RANDOM_AGE}}" - And the "XML" node "//id" should be "int" of value "{{.USER_ID}}" + And the "XML" node "//age" should be "integer" of value "{{.RANDOM_AGE}}" + And the "XML" node "//id" should be "integer" of value "{{.USER_ID}}" And the "XML" node "//description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" And the "XML" node "//friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" diff --git a/features/test_server/yaml/create.feature b/features/test_server/yaml/create.feature index 361169f..fef672b 100644 --- a/features/test_server/yaml/create.feature +++ b/features/test_server/yaml/create.feature @@ -48,9 +48,10 @@ Feature: Adding new user And the response body should have format "YAML" And time between last request and response should be less than or equal to "2s" And the "YAML" node "$.firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" - And the "YAML" node "$.lastName" should be "string" of value "doe-{{.RANDOM_LAST_NAME}}" + And the "YAML" node "$.lastName" should be "scalar" of value "doe-{{.RANDOM_LAST_NAME}}" And the "YAML" node "$.lastName" should match regExp "doe-.*" And the "YAML" node "$.age" should be "int" of value "{{.RANDOM_AGE}}" + And the "YAML" node "$.age" should be "scalar" of value "{{.RANDOM_AGE}}" And the "YAML" node "$.description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" And the "YAML" node "$.friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" diff --git a/features/test_server/yaml/get_many.feature b/features/test_server/yaml/get_many.feature index 61a36dd..3749369 100644 --- a/features/test_server/yaml/get_many.feature +++ b/features/test_server/yaml/get_many.feature @@ -81,4 +81,7 @@ Feature: Fetching many users. And the response should have header "Content-Type" of value "application/x-yaml" And the response body should have format "YAML" # here we only check only node type, not its exact value - And the "YAML" node "$." should be "slice" \ No newline at end of file + # in terms of YAML + And the "YAML" node "$[1].id" should be "scalar" + # in terms of Go + And the "YAML" node "$[1].id" should be "int" diff --git a/features/test_server/yaml/get_one.feature b/features/test_server/yaml/get_one.feature index 9188079..982e6e5 100644 --- a/features/test_server/yaml/get_one.feature +++ b/features/test_server/yaml/get_one.feature @@ -52,11 +52,11 @@ Feature: Fetching single user. And the response body should have format "YAML" And time between last request and response should be less than or equal to "2s" And the "YAML" node "$.firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" - And the "YAML" node "$.lastName" should be "string" of value "{{.RANDOM_LAST_NAME}}" + And the "YAML" node "$.lastName" should be "scalar" of value "{{.RANDOM_LAST_NAME}}" And the "YAML" node "$.age" should be "int" of value "{{.RANDOM_AGE}}" - And the "YAML" node "$.id" should be "int" of value "{{.USER_ID}}" - And the "YAML" node "$.description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" - And the "YAML" node "$.friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" + And the "YAML" node "$.id" should be "scalar" of value "{{.USER_ID}}" + And the "YAML" node "$.description" should be "scalar" of value "{{.RANDOM_DESCRIPTION}}" + And the "YAML" node "$.friendSince" should be "scalar" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" Scenario: Unsuccessful attempt to fetch not existing user As application user diff --git a/features/test_server/yaml/replace.feature b/features/test_server/yaml/replace.feature index c306929..381ec81 100644 --- a/features/test_server/yaml/replace.feature +++ b/features/test_server/yaml/replace.feature @@ -100,12 +100,12 @@ Feature: Replacing single user account. Then the response status code should be 200 And the response should have header "Content-Type" of value "application/x-yaml" And the response body should have format "YAML" - And the "YAML" node "$.firstName" should be "string" of value "{{.NEW_USER_RANDOM_FIRST_NAME}}" - And the "YAML" node "$.lastName" should be "string" of value "{{.NEW_USER_RANDOM_LAST_NAME}}" - And the "YAML" node "$.age" should be "int" of value "{{.NEW_USER_RANDOM_AGE}}" - And the "YAML" node "$.id" should be "int" of value "{{.USER_ID}}" - And the "YAML" node "$.description" should be "string" of value "{{.NEW_USER_RANDOM_DESCRIPTION}}" - And the "YAML" node "$.friendSince" should be "string" of value "{{.NEW_USER_MEET_DATE.Format `2006-01-02T15:04:05Z`}}" + And the "YAML" node "$.firstName" should be "scalar" of value "{{.NEW_USER_RANDOM_FIRST_NAME}}" + And the "YAML" node "$.lastName" should be "scalar" of value "{{.NEW_USER_RANDOM_LAST_NAME}}" + And the "YAML" node "$.age" should be "scalar" of value "{{.NEW_USER_RANDOM_AGE}}" + And the "YAML" node "$.id" should be "scalar" of value "{{.USER_ID}}" + And the "YAML" node "$.description" should be "scalar" of value "{{.NEW_USER_RANDOM_DESCRIPTION}}" + And the "YAML" node "$.friendSince" should be "scalar" of value "{{.NEW_USER_MEET_DATE.Format `2006-01-02T15:04:05Z`}}" Scenario: Unsuccessful attempt to replace user account with invalid data As application user @@ -174,12 +174,12 @@ Feature: Replacing single user account. Then the response status code should be 200 And the response should have header "Content-Type" of value "application/x-yaml" And the response body should have format "YAML" - And the "YAML" node "$.firstName" should be "string" of value "{{.RANDOM_FIRST_NAME}}" - And the "YAML" node "$.lastName" should be "string" of value "{{.RANDOM_LAST_NAME}}" - And the "YAML" node "$.age" should be "int" of value "{{.RANDOM_AGE}}" - And the "YAML" node "$.id" should be "int" of value "{{.USER_ID}}" - And the "YAML" node "$.description" should be "string" of value "{{.RANDOM_DESCRIPTION}}" - And the "YAML" node "$.friendSince" should be "string" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" + And the "YAML" node "$.firstName" should be "scalar" of value "{{.RANDOM_FIRST_NAME}}" + And the "YAML" node "$.lastName" should be "scalar" of value "{{.RANDOM_LAST_NAME}}" + And the "YAML" node "$.age" should be "scalar" of value "{{.RANDOM_AGE}}" + And the "YAML" node "$.id" should be "scalar" of value "{{.USER_ID}}" + And the "YAML" node "$.description" should be "scalar" of value "{{.RANDOM_DESCRIPTION}}" + And the "YAML" node "$.friendSince" should be "scalar" of value "{{.MEET_DATE.Format `2006-01-02T15:04:05Z`}}" Scenario: Unsuccessful attempt to replace not existing user As application user diff --git a/go.mod b/go.mod index 0c56d9a..f402b11 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/pawelWritesCode/godog-example-setup go 1.18 require ( - github.com/cucumber/godog v0.12.4 + github.com/cucumber/godog v0.12.5 github.com/joho/godotenv v1.4.0 - github.com/pawelWritesCode/gdutils v1.0.0 + github.com/pawelWritesCode/gdutils v1.1.0 github.com/spf13/pflag v1.0.5 ) @@ -26,6 +26,9 @@ require ( github.com/moul/http2curl v1.0.0 // indirect github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 // indirect github.com/pawelWritesCode/qjson v1.0.1 // indirect + github.com/tidwall/gjson v1.14.1 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect diff --git a/go.sum b/go.sum index bdcbe5c..f8cd4fb 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= -github.com/cucumber/godog v0.12.4 h1:m+vQaDztkpwpmkBIX6jlwNFJiuCMkPjz5jkrUq4SIlM= -github.com/cucumber/godog v0.12.4/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc= +github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs= +github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc= github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY= github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= @@ -178,8 +178,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pawelWritesCode/gdutils v1.0.0 h1:kCxKTDiTHbKrN0zbrqXxrOTJOeySjybtRo0kjfrNqJI= -github.com/pawelWritesCode/gdutils v1.0.0/go.mod h1:CxmFBlXCXMBWEuem6xXOPvN8Qk4DOhXh5gFe1tWjP+8= +github.com/pawelWritesCode/gdutils v1.1.0 h1:YvzR1NMGCoed0g0v5VWKXgyVzpRNmNllQun4NPHLdHk= +github.com/pawelWritesCode/gdutils v1.1.0/go.mod h1:y11CAH9zZ3XT7JQ0c3YRrNzlUC4+jz8ePoG8VKD3zPM= github.com/pawelWritesCode/qjson v1.0.1 h1:MreBuXjjQj2XROgrGK5e9rWDiwLzDO4TyMyo8IvYP9M= github.com/pawelWritesCode/qjson v1.0.1/go.mod h1:BBj5FLhYUYGE8lNCKdz+MjJab+2fFcs+s9NFDDFjjnk= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -227,6 +227,12 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= +github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +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/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= diff --git a/main_test.go b/main_test.go index 0d27b90..f33e7b0 100644 --- a/main_test.go +++ b/main_test.go @@ -77,16 +77,16 @@ func InitializeScenario(ctx *godog.ScenarioContext) { |---------------------------------------------------------------------------------------------------------------- | | This section contains utility methods for random data generation. Those methods contain creation of - | - random length runes of ASCII/UNICODE/polish/english/russian characters, - | - random length sentence of ASCII/UNICODE/polish/english/russian words, + | - random length runes of ASCII/UNICODE/polish/english/russian/japanese/emoji characters, + | - random length sentence of ASCII/UNICODE/polish/english/russian/japanese/emoji words, | - int/float from provided range, | - random bool value, | - time object moved forward/backward in time. | | Every method saves its output in scenario's cache under provided key for future use through text/template syntax. */ - ctx.Step(`^I generate a random word having from "(\d+)" to "(\d+)" of "(ASCII|UNICODE|polish|english|russian)" characters and save it as "([^"]*)"$`, scenario.IGenerateARandomRunesOfLengthWithCharactersAndSaveItAs) - ctx.Step(`^I generate a random sentence having from "(\d+)" to "(\d+)" of "(ASCII|UNICODE|polish|english|russian)" words and save it as "([^"]*)"$`, scenario.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(3, 10)) + ctx.Step(`^I generate a random word having from "(\d+)" to "(\d+)" of "(ASCII|UNICODE|polish|english|russian|japanese|emoji)" characters and save it as "([^"]*)"$`, scenario.IGenerateARandomRunesOfLengthWithCharactersAndSaveItAs) + ctx.Step(`^I generate a random sentence having from "(\d+)" to "(\d+)" of "(ASCII|UNICODE|polish|english|russian|japanese|emoji)" words and save it as "([^"]*)"$`, scenario.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(3, 10)) ctx.Step(`^I generate a random "(int|float)" in the range from "([^"]*)" to "([^"]*)" and save it as "([^"]*)"$`, scenario.IGenerateARandomNumberInTheRangeFromToAndSaveItAs) ctx.Step(`^I generate a random bool value and save it as "([^"]*)"$`, scenario.IGenerateRandomBoolValueAndSaveItAs) ctx.Step(`^I generate current time and travel "(backward|forward)" "([^"]*)" in time and save it as "([^"]*)"$`, scenario.IGenerateCurrentTimeAndTravelByAndSaveItAs) @@ -134,12 +134,12 @@ func InitializeScenario(ctx *godog.ScenarioContext) { | | Every argument following immediately after word "node" or "nodes" | should have syntax acceptable by one of json-path libraries and may contain template values: - | https://github.com/pawelWritesCode/qjson or https://github.com/oliveagle/jsonpath (JSON) + | https://github.com/tidwall/gjson or https://github.com/oliveagle/jsonpath (JSON) | https://github.com/goccy/go-yaml (YAML) | https://github.com/antchfx/xmlquery (XML) | | Method "the response should have nodes" accepts list of nodes, - | separated with comma ",". For example: "data[0].user, $.data[1].user, data". + | separated with comma ",". For example: "data.0.user, $.data.1.user, data". | | Argument in method starting with 'time between ...' should be string valid for | golang standard library time.ParseDuration func, for example: 3s, 1h, 30ms @@ -151,15 +151,18 @@ func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^the response should (not )?have cookie "([^"]*)"$`, scenario.TheResponseShouldOrShouldNotHaveCookie) ctx.Step(`^the response should have cookie "([^"]*)" of value "([^"]*)"$`, scenario.TheResponseShouldHaveCookieOfValue) + ctx.Step(`^the response cookie "([^"]*)" should (not )?match regExp "([^"]*)"$`, scenario.TheResponseCookieShouldOrShouldNotMatchRegExp) ctx.Step(`^the response status code should (not )?be (\d+)$`, scenario.TheResponseStatusCodeShouldOrShouldNotBe) ctx.Step(`^the "(JSON|YAML|XML)" response should have nodes "([^"]*)"$`, scenario.TheResponseShouldHaveNodes) ctx.Step(`^the "(JSON|YAML|XML)" response should (not )?have node "([^"]*)"$`, scenario.TheResponseShouldOrShouldNotHaveNode) - ctx.Step(`^the "(JSON|YAML|XML)" node "([^"]*)" should be "(string|int|float|bool)" of value "([^"]*)"$`, scenario.TheNodeShouldBeOfValue) + ctx.Step(`^the "(JSON|YAML|XML)" node "([^"]*)" should be "(bool|boolean|float|int|integer|number|scalar|string)" of value "([^"]*)"$`, scenario.TheNodeShouldBeOfValue) + ctx.Step(`^the "(JSON|YAML|XML)" node "([^"]*)" should be "(bool|boolean|float|int|integer|number|scalar|string)" and contain one of values "([^"]*)"$`, scenario.TheNodeShouldBeOfValues) + ctx.Step(`^the "(JSON|YAML|XML)" node "([^"]*)" should (not )?contain sub string "([^"]*)"$`, scenario.TheNodeShouldOrShouldNotContainSubString) ctx.Step(`^the "(JSON|YAML|XML)" node "([^"]*)" should (not )?be slice of length "(\d+)"$`, scenario.TheNodeShouldOrShouldNotBeSliceOfLength) - ctx.Step(`^the "(JSON|YAML)" node "([^"]*)" should (not )?be "(nil|string|int|float|bool|map|slice)"$`, scenario.TheNodeShouldOrShouldNotBe) + ctx.Step(`^the "(JSON|YAML|XML)" node "([^"]*)" should (not )?be "(array|bool|boolean|float|int|integer|map|mapping|nil|null|number|object|sequence|scalar|slice|string)"$`, scenario.TheNodeShouldOrShouldNotBe) ctx.Step(`^the "(JSON|YAML|XML)" node "([^"]*)" should (not )?match regExp "([^"]*)"$`, scenario.TheNodeShouldOrShouldNotMatchRegExp) ctx.Step(`^the "(JSON)" node "([^"]*)" should be valid according to schema "([^"]*)"$`, scenario.IValidateNodeWithSchemaReference) ctx.Step(`^the "(JSON)" node "([^"]*)" should be valid according to schema:$`, scenario.IValidateNodeWithSchemaString) @@ -179,7 +182,7 @@ func InitializeScenario(ctx *godog.ScenarioContext) { | | Argument following immediately after word "node" | should have syntax acceptable by one of path libraries and may contain template values: - | https://github.com/pawelWritesCode/qjson or https://github.com/oliveagle/jsonpath (JSON) + | https://github.com/tidwall/gjson or https://github.com/oliveagle/jsonpath (JSON) | https://github.com/goccy/go-yaml (YAML) | https://github.com/antchfx/xmlquery (XML) */ @@ -195,6 +198,7 @@ func InitializeScenario(ctx *godog.ScenarioContext) { | This section contains methods that are useful for debugging during test creation phase. */ ctx.Step(`^I print last response body$`, scenario.IPrintLastResponseBody) + ctx.Step(`^I print cache data$`, scenario.IPrintCacheData) ctx.Step(`^I start debug mode$`, scenario.IStartDebugMode) ctx.Step(`^I stop debug mode$`, scenario.IStopDebugMode)