From 49df6f6b1b3903667ac147a906edfb4be8ee3c4b Mon Sep 17 00:00:00 2001 From: Lev Marder Date: Mon, 29 Aug 2022 15:51:08 +0300 Subject: [PATCH 1/2] feature: cases variables through tests --- examples/with-cases-example/cases/do.yaml | 25 ++++++++ examples/with-cases-example/func_test.go | 28 +++++++++ examples/with-cases-example/main.go | 62 +++++++++++++++++++ models/test.go | 1 + runner/runner.go | 4 +- testloader/yaml_file/parser.go | 27 +++++++- testloader/yaml_file/parser_test.go | 2 + testloader/yaml_file/test.go | 6 ++ testloader/yaml_file/test_definition.go | 33 +++++----- testloader/yaml_file/test_variables_test.go | 40 ++++++++++-- .../testdata/combined-variables.yaml | 32 ++++++++++ 11 files changed, 237 insertions(+), 23 deletions(-) create mode 100644 examples/with-cases-example/cases/do.yaml create mode 100644 examples/with-cases-example/func_test.go create mode 100644 examples/with-cases-example/main.go create mode 100644 testloader/yaml_file/testdata/combined-variables.yaml diff --git a/examples/with-cases-example/cases/do.yaml b/examples/with-cases-example/cases/do.yaml new file mode 100644 index 0000000..780916f --- /dev/null +++ b/examples/with-cases-example/cases/do.yaml @@ -0,0 +1,25 @@ +- name: Test /do endpoint + method: POST + path: /do + + variables: + name: name + + request: '{"{{ $name }}": "{{ .name }}"}' + + response: + 200: '{"{{ $num }}":{{ .num }}}' + + cases: + - variables: + num: num + requestArgs: + name: a + responseArgs: + 200: + num: 1 + - requestArgs: + name: b + responseArgs: + 200: + num: 2 \ No newline at end of file diff --git a/examples/with-cases-example/func_test.go b/examples/with-cases-example/func_test.go new file mode 100644 index 0000000..393be00 --- /dev/null +++ b/examples/with-cases-example/func_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "net/http/httptest" + "os" + "testing" + + "github.com/lamoda/gonkey/mocks" + "github.com/lamoda/gonkey/runner" +) + +func TestProxy(t *testing.T) { + m := mocks.NewNop("backend") + if err := m.Start(); err != nil { + t.Fatal(err) + } + defer m.Shutdown() + + os.Setenv("BACKEND_ADDR", m.Service("backend").ServerAddr()) + initServer() + srv := httptest.NewServer(nil) + + runner.RunWithTesting(t, &runner.RunWithTestingParams{ + Server: srv, + TestsDir: "cases", + Mocks: m, + }) +} diff --git a/examples/with-cases-example/main.go b/examples/with-cases-example/main.go new file mode 100644 index 0000000..ea0d576 --- /dev/null +++ b/examples/with-cases-example/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" +) + +type Request struct { + Name string `json:"name"` +} + +type Response struct { + Num int `json:"num"` +} + +func main() { + initServer() + log.Fatal(http.ListenAndServe(":8080", nil)) +} + +func initServer() { + http.HandleFunc("/do", Do) +} + +func Do(w http.ResponseWriter, r *http.Request) { + var response Response + + if r.Method != "POST" { + response.Num = 0 + fmt.Fprint(w, buildResponse(response)) + return + } + + jsonRequest, _ := ioutil.ReadAll(r.Body) + request := buildRequest(jsonRequest) + + if request.Name == "a" { + response.Num = 1 + fmt.Fprint(w, buildResponse(response)) + return + } + + response.Num = 2 + fmt.Fprint(w, buildResponse(response)) +} + +func buildRequest(jsonRequest []byte) Request { + var request Request + + json.Unmarshal(jsonRequest, &request) + + return request +} + +func buildResponse(response Response) string { + jsonResponse, _ := json.Marshal(response) + + return string(jsonResponse) +} diff --git a/models/test.go b/models/test.go index 061644a..ff6b108 100644 --- a/models/test.go +++ b/models/test.go @@ -35,6 +35,7 @@ type TestInterface interface { DbQueryString() string DbResponseJson() []string GetVariables() map[string]string + GetCombinedVariables() map[string]string GetVariablesToSet() map[int]map[string]string GetDatabaseChecks() []DatabaseCheck SetDatabaseChecks([]DatabaseCheck) diff --git a/runner/runner.go b/runner/runner.go index 757762f..aaa8251 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -145,7 +145,7 @@ func (r *Runner) executeTest(v models.TestInterface, client *http.Client) (*mode } } - r.config.Variables.Load(v.GetVariables()) + r.config.Variables.Load(v.GetCombinedVariables()) v = r.config.Variables.Apply(v) // load fixtures @@ -231,7 +231,7 @@ func (r *Runner) executeTest(v models.TestInterface, client *http.Client) (*mode return nil, err } - r.config.Variables.Load(v.GetVariables()) + r.config.Variables.Load(v.GetCombinedVariables()) v = r.config.Variables.Apply(v) for _, c := range r.checkers { diff --git a/testloader/yaml_file/parser.go b/testloader/yaml_file/parser.go index 90f8d2e..e73adc6 100644 --- a/testloader/yaml_file/parser.go +++ b/testloader/yaml_file/parser.go @@ -4,12 +4,22 @@ import ( "bytes" "fmt" "io/ioutil" + "regexp" + "strings" "text/template" "github.com/lamoda/gonkey/models" + "gopkg.in/yaml.v2" ) +const ( + gonkeyVariableLeftPart = "{{ $" + gonkeyProtectSubstitute = "!protect!" +) + +var gonkeyProtectTemplate = regexp.MustCompile("{{\\s*\\$") + func parseTestDefinitionFile(absPath string) ([]Test, error) { data, err := ioutil.ReadFile(absPath) if err != nil { @@ -37,6 +47,8 @@ func parseTestDefinitionFile(absPath string) ([]Test, error) { } func substituteArgs(tmpl string, args map[string]interface{}) (string, error) { + tmpl = gonkeyProtectTemplate.ReplaceAllString(tmpl, gonkeyProtectSubstitute) + compiledTmpl, err := template.New("").Parse(tmpl) if err != nil { return "", err @@ -48,7 +60,9 @@ func substituteArgs(tmpl string, args map[string]interface{}) (string, error) { return "", err } - return buf.String(), nil + tmpl = strings.ReplaceAll(buf.String(), gonkeyProtectSubstitute, gonkeyVariableLeftPart) + + return tmpl, nil } func substituteArgsToMap(tmpl map[string]string, args map[string]interface{}) (map[string]string, error) { @@ -77,6 +91,7 @@ func makeTestFromDefinition(filePath string, testDefinition TestDefinition) ([]T test.AfterRequestScript = testDefinition.AfterRequestScriptParams.PathTmpl test.DbQuery = testDefinition.DbQueryTmpl test.DbResponse = testDefinition.DbResponseTmpl + test.CombinedVariables = testDefinition.Variables dbChecks := []models.DatabaseCheck{} for _, check := range testDefinition.DatabaseChecks { @@ -97,6 +112,11 @@ func makeTestFromDefinition(filePath string, testDefinition TestDefinition) ([]T headersValTmpl := testDefinition.HeadersVal cookiesValTmpl := testDefinition.CookiesVal responseHeadersTmpl := testDefinition.ResponseHeaders + combinedVariables := map[string]string{} + + if testDefinition.Variables != nil { + combinedVariables = testDefinition.Variables + } // produce as many tests as cases defined for caseIdx, testCase := range testDefinition.Cases { @@ -175,6 +195,11 @@ func makeTestFromDefinition(filePath string, testDefinition TestDefinition) ([]T return nil, err } + for key, value := range testCase.Variables { + combinedVariables[key] = value.(string) + } + test.CombinedVariables = combinedVariables + // compile DbResponse if testCase.DbResponse != nil { // DbResponse from test case has top priority diff --git a/testloader/yaml_file/parser_test.go b/testloader/yaml_file/parser_test.go index 4af92da..d91da72 100644 --- a/testloader/yaml_file/parser_test.go +++ b/testloader/yaml_file/parser_test.go @@ -50,6 +50,8 @@ var testsYAMLData = ` 200: foo: 'Hello world' bar: 42 + variables: + newVar: some_value ` func TestParseTestsWithCases(t *testing.T) { diff --git a/testloader/yaml_file/test.go b/testloader/yaml_file/test.go index 023e900..3707ec2 100644 --- a/testloader/yaml_file/test.go +++ b/testloader/yaml_file/test.go @@ -29,6 +29,8 @@ type Test struct { DbQuery string DbResponse []string + CombinedVariables map[string]string + DbChecks []models.DatabaseCheck } @@ -147,6 +149,10 @@ func (t *Test) GetVariables() map[string]string { return t.Variables } +func (t *Test) GetCombinedVariables() map[string]string { + return t.CombinedVariables +} + func (t *Test) GetForm() *models.Form { return t.Form } diff --git a/testloader/yaml_file/test_definition.go b/testloader/yaml_file/test_definition.go index 2fa94a5..9f57697 100644 --- a/testloader/yaml_file/test_definition.go +++ b/testloader/yaml_file/test_definition.go @@ -39,6 +39,7 @@ type CaseData struct { DbQueryArgs map[string]interface{} `json:"dbQueryArgs" yaml:"dbQueryArgs"` DbResponseArgs map[string]interface{} `json:"dbResponseArgs" yaml:"dbResponseArgs"` DbResponse []string `json:"dbResponse" yaml:"dbResponse"` + Variables map[string]interface{} `json:"variables" yaml:"variables"` } type DatabaseCheck struct { @@ -55,22 +56,22 @@ type VariablesToSet map[int]map[string]string /* There can be two types of data in yaml-file: -1) JSON-paths: - VariablesToSet: - : - : - : -2) Plain text: - VariablesToSet: - : - : - ... - In this case we unmarshall values to format similar to JSON-paths format with empty paths: - VariablesToSet: - : - : "" - : - : "" + 1. JSON-paths: + VariablesToSet: + : + : + : + 2. Plain text: + VariablesToSet: + : + : + ... + In this case we unmarshall values to format similar to JSON-paths format with empty paths: + VariablesToSet: + : + : "" + : + : "" */ func (v *VariablesToSet) UnmarshalYAML(unmarshal func(interface{}) error) error { diff --git a/testloader/yaml_file/test_variables_test.go b/testloader/yaml_file/test_variables_test.go index 345ced3..0d85181 100644 --- a/testloader/yaml_file/test_variables_test.go +++ b/testloader/yaml_file/test_variables_test.go @@ -50,12 +50,31 @@ func TestParseTestsWithVariables(t *testing.T) { testApplied := vars.Apply(testOriginal) // check that original test is not changed - checkOriginal(t, testOriginal) + checkOriginal(t, testOriginal, false) - checkApplied(t, testApplied) + checkApplied(t, testApplied, false) } -func checkOriginal(t *testing.T, test models.TestInterface) { +func TestParseTestsWithCombinedVariables(t *testing.T) { + + tests, err := parseTestDefinitionFile("testdata/combined-variables.yaml") + require.NoError(t, err) + + testOriginal := &tests[0] + + vars := variables.New() + vars.Load(testOriginal.GetCombinedVariables()) + assert.NoError(t, err) + + testApplied := vars.Apply(testOriginal) + + // check that original test is not changed + checkOriginal(t, testOriginal, true) + + checkApplied(t, testApplied, true) +} + +func checkOriginal(t *testing.T, test models.TestInterface, combined bool) { t.Helper() @@ -75,9 +94,15 @@ func checkOriginal(t *testing.T, test models.TestInterface) { resp, ok = test.GetResponse(404) assert.True(t, ok) assert.Equal(t, "{{ $respRx }}", resp) + + if combined { + resp, ok = test.GetResponse(501) + assert.True(t, ok) + assert.Equal(t, "{{ $newVar }} - {{ $redefinedVar }}", resp) + } } -func checkApplied(t *testing.T, test models.TestInterface) { +func checkApplied(t *testing.T, test models.TestInterface, combined bool) { t.Helper() @@ -109,4 +134,11 @@ func checkApplied(t *testing.T, test models.TestInterface) { mockBody, ok := mockMap["body"] assert.True(t, ok) assert.Equal(t, "{\"reqParam\": \"reqParam_value\"}", mockBody) + + if combined { + resp, ok = test.GetResponse(501) + assert.True(t, ok) + t.Log(resp) + assert.Equal(t, "some_value - redefined_value", resp) + } } diff --git a/testloader/yaml_file/testdata/combined-variables.yaml b/testloader/yaml_file/testdata/combined-variables.yaml new file mode 100644 index 0000000..d49074f --- /dev/null +++ b/testloader/yaml_file/testdata/combined-variables.yaml @@ -0,0 +1,32 @@ +- method: "{{ $method }}" + path: "/some/path/{{ $pathPart }}" + variables: + tag: "some_tag" + reqParam: "reqParam_value" + method: "POST" + pathPart: "part_of_path" + query: "query_val" + header: "header_val" + resp: "resp_val" + respRx: "$matchRegexp(^[0-9.]+$)" + jsonParam: "jsonParam_val" + existingVar: "existingVar_Value" + redefinedVar: "initial_value" + query: "{{ $query }}" + headers: + header1: "{{ $header }}" + request: '{"reqParam": "{{ $reqParam }}"}' + response: + 200: "{{ $resp }}" + 404: "{{ $respRx }}" + 500: "{{ $existingVar }} - {{ $notExistingVar }}" + 501: "{{ $newVar }} - {{ $redefinedVar }}" + mocks: + server: + strategy: constant + body: '{"reqParam": "{{ $reqParam }}"}' + statusCode: 200 + cases: + - variables: + newVar: "some_value" + redefinedVar: "redefined_value" From 0b4f879d3083dc96798f9ad96d843fa618cb3435 Mon Sep 17 00:00:00 2001 From: Lev Marder Date: Mon, 19 Sep 2022 12:36:49 +0300 Subject: [PATCH 2/2] added cases variables to readme --- README-ru.md | 20 ++++++++++++++++++++ README.md | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/README-ru.md b/README-ru.md index dbbadb2..28d8b0d 100644 --- a/README-ru.md +++ b/README-ru.md @@ -461,6 +461,26 @@ password=private_password env-файл, например, удобно использовать, когда нужно вынести из теста приватную информацию (пароли, ключи и т.п.) +#### В cases + +Переменные могут быть заданы в блоке *cases*. + +Пример: + +```yaml +- name: Get user info + method: GET + path: "/user/1" + response: + 200: '{ "user_id": "1", "name": "{{ $name }}", "surname": "{{ $surname }}" }' + cases: + - variables: + name: John + surname: Doe +``` + +Такие переменные будут доступны и в других кейсах, если не будут переопределены. + ## Загрузка файлов В тестовом запросе можно загружать файлы. Для этого нужно указать тип запроса - POST и заголовок: diff --git a/README.md b/README.md index d2da4e3..88e6cf5 100644 --- a/README.md +++ b/README.md @@ -461,6 +461,26 @@ password=private_password env-file can be convenient to hide sensitive information from a test (passwords, keys, etc.) +#### From cases + +You can describe variables in *cases* section of a test. + +Example: + +```yaml +- name: Get user info + method: GET + path: "/user/1" + response: + 200: '{ "user_id": "1", "name": "{{ $name }}", "surname": "{{ $surname }}" }' + cases: + - variables: + name: John + surname: Doe +``` + +Variables like these will be available through another cases if not redefined. + ## Files uploading You can upload files in test request. For this you must specify the type of request - POST and header: