From 84ad08c932cc051341544c2921bafb89f655e03d Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 23 Jan 2019 12:47:43 +0100 Subject: [PATCH 01/17] circleci. Testing --- .circleci/config.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ebad86b..5d61148 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,5 @@ version: 2 jobs: - deploy: - machine: - enabled: true - steps: - - run: - name: Deploy to fylr.io - command: ssh ec2-user@18.194.217.112 ./fylr.io/deploy.sh test: docker: - image: circleci/golang:1.11 @@ -19,12 +12,6 @@ jobs: - run: make test workflows: version: 2 - build-and-deploy: - jobs: - - deploy: - filters: - branches: - only: master build-and-test: jobs: - test \ No newline at end of file From df394f0e64f950ac6c8bcb732c82643088b7381a Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 23 Jan 2019 14:17:05 +0100 Subject: [PATCH 02/17] cjson.go: Print lines of json file on error (See #51849) --- lib/cjson/cjson.go | 17 +++++++++++++---- lib/template/template_loader.go | 3 ++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/cjson/cjson.go b/lib/cjson/cjson.go index 6c99939..7a9ed06 100644 --- a/lib/cjson/cjson.go +++ b/lib/cjson/cjson.go @@ -1,14 +1,16 @@ package cjson import ( + "bufio" "encoding/json" "fmt" "regexp" + "strings" ) func Unmarshal(input []byte, output interface{}) error { - var commentRegex = regexp.MustCompile(`(?m)^(.*?)(#|//).*$`) - inputNoComments := []byte(commentRegex.ReplaceAllString(string(input), `$1`)) + var commentRegex = regexp.MustCompile(`(?m)^[\t ]*(#|//).*$`) + inputNoComments := []byte(commentRegex.ReplaceAllString(string(input), ``)) // unmarshal into object err := json.Unmarshal(inputNoComments, output) @@ -27,6 +29,13 @@ func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { } func getIndepthJsonError(input []byte, inputError error) (err error) { + jsonWithLineNumbers := "\n" + scanner := bufio.NewScanner(strings.NewReader(string(input))) + i := 1 + for scanner.Scan() { + jsonWithLineNumbers = fmt.Sprintf("%s%d %s\n", jsonWithLineNumbers, i, scanner.Text()) + i++ + } err = inputError @@ -38,7 +47,7 @@ func getIndepthJsonError(input []byte, inputError error) (err error) { } err = fmt.Errorf("Cannot parse JSON '%s' schema due to a syntax error at line %d, character %d: %v", - string(input), line, character, jsonError.Error()) + string(jsonWithLineNumbers), line, character, jsonError.Error()) return } @@ -50,7 +59,7 @@ func getIndepthJsonError(input []byte, inputError error) (err error) { } return fmt.Errorf(`In JSON '%s', the type '%v' cannot be converted into the Go '%v' type on struct '%s', field '%v'. See input file line %d, character %d`, - string(input), jsonError.Value, jsonError.Type.Name(), jsonError.Struct, jsonError.Field, line, character) + string(jsonWithLineNumbers), jsonError.Value, jsonError.Type.Name(), jsonError.Struct, jsonError.Field, line, character) } return diff --git a/lib/template/template_loader.go b/lib/template/template_loader.go index 6decdc9..dd444be 100644 --- a/lib/template/template_loader.go +++ b/lib/template/template_loader.go @@ -76,8 +76,9 @@ func (loader *Loader) Render( ctx interface{}) (res []byte, err error) { //Remove comments from template + var re = regexp.MustCompile(`(?m)^[\t ]*(#|//).*$`) - tmplBytes = []byte(re.ReplaceAllString(string(tmplBytes), ``)) + tmplBytes = []byte(re.ReplaceAllString(string(tmplBytes), `$1`)) var funcMap template.FuncMap From 9536cf7cc448fee6f2d654b21d389d6eef9c9ed1 Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 23 Jan 2019 14:47:22 +0100 Subject: [PATCH 03/17] Remove the complete session logic, so that the apitest tool can be used apart from a easydb setup --- README.md | 23 +-- api_testcase.go | 44 ++-- api_testcase_test.go | 17 +- api_testsuite.go | 61 ++---- config.go | 29 +-- config_test.go | 20 +- go.mod | 1 + lib/api/build_policies_test.go | 4 +- lib/api/datastore_test.go | 2 +- lib/api/request.go | 61 +++++- lib/api/request_test.go | 6 +- lib/api/response_test.go | 2 +- lib/api/session.go | 244 ----------------------- lib/api/session_test.go | 89 --------- lib/cjson/cjson_test.go | 90 ++++----- lib/compare/comparison_functions_test.go | 2 +- lib/csv/csv.go | 2 + lib/template/template_funcs_test.go | 2 +- lib/template/template_loader_test.go | 8 +- lib/test_utils/assert.go | 184 ----------------- lib/test_utils/server.go | 26 --- lib/test_utils/test_utils.go | 3 +- main.go | 231 +++++++++++---------- 23 files changed, 276 insertions(+), 875 deletions(-) delete mode 100644 lib/api/session.go delete mode 100644 lib/api/session_test.go delete mode 100644 lib/test_utils/assert.go delete mode 100644 lib/test_utils/server.go diff --git a/README.md b/README.md index 659bf31..e2ab49f 100644 --- a/README.md +++ b/README.md @@ -101,12 +101,6 @@ Manifest is loaded as **template**, so you can use variables, Go **range** and * ```json { - //Defines how a single test (if defined in the single testcase) should authenticate against the easydb. The authentication is valid for the complete testsuite and only one session can be used. - "authentication": { - "login": "root", - "method": "easydb", // This is the method given in the easydb doc - "password": "admin" - }, //General info about the testuite. Try to explain your problem indepth here. So that someone who works on the test years from now knows what is happening "description": "search api tests for filename", //Testname. Should be the ticket number if the test is based on a ticket @@ -143,16 +137,6 @@ Manifest is loaded as **template**, so you can use variables, Go **range** and * "continue_on_failure": true, //Name to identify this single test. Is important for the log. Try to give an explaning name "name": "Testname", - //Defines how this single test should authenticate against the easydb. This authentication is valid only for the single test and overwrites the testuite setting - "authentication": { - "login": "root", - "method": "easydb", - "password": "admin", - // Store parts of the session repsonse - "store_response_qjson": { - "max_event_id": "body.current_max_event_id" - }, - }, // Store custom values to the datastore "store": { "key1": "value1", @@ -233,12 +217,15 @@ The custom storage is persistent throughout the **apitest** run, so all requirem The custom store uses a **string** as index and can store any type of data. -If an **key** ends in `[]`, the value is assumed to be an Array, and is appended. If no Array exists, an array is created. +**Array**: If an key ends in `[]`, the value is assumed to be an Array, and is appended. If no Array exists, an array is created. + +**Map**: If an key ends in `[key]`, the value is assumed to be an map, and writes the data into the map at that key. If no map exists, an map is created. ```django { "store": { - "eas_ids[]": 15 + "eas_ids[]": 15, + "mapStorage[keyIWantToStore]": "value" } } ``` diff --git a/api_testcase.go b/api_testcase.go index c058c41..b2e61d2 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -24,13 +24,12 @@ const ( // Case defines the structure of our single testcase // It gets read in by our config reader at the moment the mainfest.json gets parsed type Case struct { - Name string `json:"name"` - RequestData *util.GenericJson `json:"request"` - ResponseData util.GenericJson `json:"response"` - ContinueOnFailure bool `json:"continue_on_failure"` - Authentication *api.SessionAuthentication `json:"authentication"` - Store map[string]interface{} `json:"store"` // init datastore before testrun - StoreResponse map[string]string `json:"store_response_qjson"` // store qjson parsed response in datastore + Name string `json:"name"` + RequestData *util.GenericJson `json:"request"` + ResponseData util.GenericJson `json:"response"` + ContinueOnFailure bool `json:"continue_on_failure"` + Store map[string]interface{} `json:"store"` // init datastore before testrun + StoreResponse map[string]string `json:"store_response_qjson"` // store qjson parsed response in datastore Timeout int `json:"timeout_ms"` Delay *int `json:"delay_ms"` @@ -38,11 +37,12 @@ type Case struct { CollectResponse util.GenericJson `json:"collect_response"` loader template.Loader - session api.Session manifestDir string reporter *report.Report suiteIndex int index int + dataStore *api.Datastore + ServerURL string } func (testCase Case) runAPITestCase() (success bool) { @@ -178,7 +178,7 @@ func (testCase Case) executeRequest(counter int) ( err error) { // Store datastore - err = testCase.session.Store.SetMap(testCase.Store) + err = testCase.dataStore.SetMap(testCase.Store) if err != nil { err = fmt.Errorf("error setting datastore map:%s", err) } @@ -191,15 +191,15 @@ func (testCase Case) executeRequest(counter int) ( } apitestLogging.DebugWithVerbosityf( apitestLogging.V1, - "request: %s", req.ToString(testCase.session)) - apiResp, err = testCase.session.SendRequest(req) + "request: %s", req.ToString()) + apiResp, err = req.Send() if err != nil { err = fmt.Errorf("error sending request: %s", err) return } // Store in custom store - err = testCase.session.Store.SetWithQjson(apiResp, testCase.StoreResponse) + err = testCase.dataStore.SetWithQjson(apiResp, testCase.StoreResponse) if err != nil { err = fmt.Errorf("error store repsonse with qjson: %s", err) return @@ -215,9 +215,9 @@ func (testCase Case) executeRequest(counter int) ( } // Store in datastore -1 list if counter == 0 { - testCase.session.Store.AppendResponse(json) + testCase.dataStore.AppendResponse(json) } else { - testCase.session.Store.UpdateLastResponse(json) + testCase.dataStore.UpdateLastResponse(json) } } @@ -239,10 +239,10 @@ func (testCase Case) executeRequest(counter int) ( return } -func LogReqResp(request api.Request, response api.Response, session api.Session) { +func LogReqResp(request api.Request, response api.Response) { apitestLogging.DebugWithVerbosityf( apitestLogging.V0, - "[Request] %s", request.ToString(session)) + "[Request] %s", request.ToString()) apitestLogging.DebugWithVerbosityf( apitestLogging.V0, "[Response] %s", response.ToString()) @@ -276,18 +276,18 @@ func (testCase Case) run() (success bool, err error) { breakPresent, err := testCase.breakResponseIsPresent(request, apiResponse) if err != nil { - LogReqResp(request, apiResponse, testCase.session) + LogReqResp(request, apiResponse) return false, fmt.Errorf("error checking for break response: %s", err) } if breakPresent { - LogReqResp(request, apiResponse, testCase.session) + LogReqResp(request, apiResponse) return false, fmt.Errorf("Break response found") } collectLeft, err := testCase.checkCollectResponse(request, apiResponse) if err != nil { - LogReqResp(request, apiResponse, testCase.session) + LogReqResp(request, apiResponse) return false, fmt.Errorf("error checking for continue response: %s", err) } @@ -328,7 +328,7 @@ func (testCase Case) run() (success bool, err error) { for _, v := range collectArray { jsonV, err := json.Marshal(v) if err != nil { - LogReqResp(request, apiResponse, testCase.session) + LogReqResp(request, apiResponse) return false, err } logging.Warnf("Collect response not found: %s", jsonV) @@ -336,7 +336,7 @@ func (testCase Case) run() (success bool, err error) { } } - LogReqResp(request, apiResponse, testCase.session) + LogReqResp(request, apiResponse) return false, nil } return true, nil @@ -390,6 +390,8 @@ func (testCase Case) loadRequestSerialization() (spec api.Request, err error) { } err = cjson.Unmarshal(specBytes, &spec) spec.ManifestDir = testCase.manifestDir + spec.DataStore = testCase.dataStore + spec.ServerURL = testCase.ServerURL return } diff --git a/api_testcase_test.go b/api_testcase_test.go index 5286c3f..4b12500 100644 --- a/api_testcase_test.go +++ b/api_testcase_test.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/programmfabrik/fylr-apitest/lib/api" - "github.com/programmfabrik/fylr-apitest/lib/report" "github.com/programmfabrik/fylr-apitest/lib/filesystem" + "github.com/programmfabrik/fylr-apitest/lib/report" "github.com/spf13/afero" ) @@ -68,7 +68,8 @@ func TestCollectResponseShouldWork(t *testing.T) { t.Fatal(err) } test.reporter = r - test.session, _ = api.NewSession(ts.URL, &http.Client{}, &api.SessionAuthentication{}, api.NewStore()) + test.ServerURL = ts.URL + test.dataStore = api.NewStore() test.runAPITestCase() @@ -120,7 +121,8 @@ func TestCollectLoadExternalFile(t *testing.T) { t.Fatal(err) } test.reporter = r - test.session, _ = api.NewSession(ts.URL, &http.Client{}, &api.SessionAuthentication{}, api.NewStore()) + test.ServerURL = ts.URL + test.dataStore = api.NewStore() test.runAPITestCase() @@ -180,7 +182,8 @@ func TestCollectLoadExternalCollect(t *testing.T) { t.Fatal(err) } test.reporter = r - test.session, _ = api.NewSession(ts.URL, &http.Client{}, &api.SessionAuthentication{}, api.NewStore()) + test.ServerURL = ts.URL + test.dataStore = api.NewStore() test.runAPITestCase() @@ -279,7 +282,8 @@ func TestCollectEvents(t *testing.T) { t.Fatal(err) } test.reporter = r - test.session, _ = api.NewSession(ts.URL, &http.Client{}, &api.SessionAuthentication{}, api.NewStore()) + test.ServerURL = ts.URL + test.dataStore = api.NewStore() test.runAPITestCase() @@ -331,7 +335,8 @@ func TestCollectResponseShouldFail(t *testing.T) { t.Fatal(err) } test.reporter = r - test.session, _ = api.NewSession(ts.URL, &http.Client{}, &api.SessionAuthentication{}, api.NewStore()) + test.ServerURL = ts.URL + test.dataStore = api.NewStore() test.runAPITestCase() diff --git a/api_testsuite.go b/api_testsuite.go index 3e3f70f..c5e3e45 100644 --- a/api_testsuite.go +++ b/api_testsuite.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "net/http" "path/filepath" "time" @@ -12,23 +11,21 @@ import ( "github.com/programmfabrik/fylr-apitest/lib/api" "github.com/programmfabrik/fylr-apitest/lib/cjson" - "github.com/programmfabrik/fylr-apitest/lib/report" - "github.com/programmfabrik/fylr-apitest/lib/template" "github.com/programmfabrik/fylr-apitest/lib/filesystem" "github.com/programmfabrik/fylr-apitest/lib/logging" + "github.com/programmfabrik/fylr-apitest/lib/report" + "github.com/programmfabrik/fylr-apitest/lib/template" ) // Suite defines the structure of our apitest // We do read this in with the config loader type Suite struct { - Name string `json:"name"` - Description string `json:"description"` - Authentication *api.SessionAuthentication `json:"authentication"` - Tests []util.GenericJson `json:"tests"` - RequirePaths []string `json:"require"` - Store map[string]interface{} `json:"store"` - - Client *http.Client + Name string `json:"name"` + Description string `json:"description"` + Tests []util.GenericJson `json:"tests"` + RequirePaths []string `json:"require"` + Store map[string]interface{} `json:"store"` + Config TestToolConfig datastore *api.Datastore manifestDir string @@ -36,12 +33,12 @@ type Suite struct { reporter *report.Report index int executeRequirements bool + serverURL string } // NewTestSuite creates a new suite on which we execute our tests on // Normally this only gets call from within the apitest main command func NewTestSuite( - client *http.Client, config TestToolConfig, manifestPath string, r *report.Report, @@ -50,7 +47,6 @@ func NewTestSuite( index int, ) (suite Suite, err error) { suite = Suite{ - Client: client, Config: config, manifestDir: filepath.Dir(manifestPath), manifestPath: manifestPath, @@ -120,7 +116,6 @@ func (ats Suite) runRequirements() (success bool) { logging.Infof("[%2d] %s", ats.index, "run requirements") for _, parentPath := range ats.RequirePaths { suite, err := NewTestSuite( - ats.Client, ats.Config, filepath.Join(ats.manifestDir, parentPath), r, @@ -159,21 +154,9 @@ func (ats Suite) runTestCases() (success bool) { }()*/ r := ats.reporter + //datastoreShare := api.NewStoreShare(ats.datastore) - datastoreShare := api.NewStoreShare(ats.datastore) - - loader := template.NewLoader(datastoreShare) - suiteSession, err := api.NewSession( - ats.Config.ServerURL, - ats.Client, - ats.Authentication, - datastoreShare) - if err != nil { - r.SaveToReportLog(err.Error()) - logging.Error(err) - - return false - } + loader := template.NewLoader(ats.datastore) for k, v := range ats.Tests { tests := make([]Case, 0) @@ -211,26 +194,8 @@ func (ats Suite) runTestCases() (success bool) { test.reporter = r test.suiteIndex = ats.index test.index = k - - if test.Authentication != nil { - test.session, err = api.NewSession( - ats.Config.ServerURL, - ats.Client, - test.Authentication, - datastoreShare, - ) - if err != nil { - r.SaveToReportLog(err.Error()) - logging.Error(err) - if test.ContinueOnFailure { - continue - } else { - return false - } - } - } else { - test.session = suiteSession - } + test.dataStore = ats.datastore + test.ServerURL = ats.Config.ServerURL success := test.runAPITestCase() diff --git a/config.go b/config.go index a05c7bc..420c3ce 100644 --- a/config.go +++ b/config.go @@ -2,17 +2,14 @@ package main import ( "fmt" - "github.com/programmfabrik/fylr-apitest/lib/api" "github.com/programmfabrik/fylr-apitest/lib/filesystem" "github.com/spf13/afero" "github.com/spf13/viper" "log" - "net/http" "os" "path/filepath" "time" - ) - +) type FylrConfigStruct struct { Fylr struct { @@ -33,7 +30,6 @@ type FylrConfigStruct struct { } } - var FylrConfig FylrConfigStruct var startTime time.Time @@ -55,11 +51,6 @@ func LoadConfig(cfgFile string) { } -func GetStartTime() time.Time { - return startTime -} - - // TestToolConfig gives us the basic testtool infos type TestToolConfig struct { ServerURL string @@ -75,9 +66,6 @@ func NewTestToolConfig(serverURL, dataBaseName string, rootDirectory []string) ( DataBaseName: dataBaseName, rootDirectorys: rootDirectory, } - if err = config.checkDataBase(); err != nil { - return config, fmt.Errorf("error checking database names: %s", err) - } err = config.extractTestDirectories() return config, err } @@ -105,18 +93,3 @@ func (config *TestToolConfig) extractTestDirectories() error { } return nil } - -func (config *TestToolConfig) checkDataBase() error { - session, err := api.NewSession(config.ServerURL, &http.Client{}, nil, nil) - if err != nil { - return err - } - resp, err := session.SendSettingsRequest() - if err != nil { - return fmt.Errorf("error sending settings request: %s", err) - } - if resp.DbName != config.DataBaseName { - return fmt.Errorf("db settings differ: %s != %s", resp.DbName, config.DataBaseName) - } - return nil -} diff --git a/config_test.go b/config_test.go index d20b1ff..3f63452 100644 --- a/config_test.go +++ b/config_test.go @@ -1,14 +1,13 @@ package main import ( - "fmt" "net/http" "net/http/httptest" "path/filepath" "testing" "github.com/programmfabrik/fylr-apitest/lib/filesystem" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" + "github.com/programmfabrik/go-test-utils" "github.com/spf13/afero" ) @@ -50,23 +49,6 @@ func SetupFS() { } -func TestTestToolConfig_DBName(t *testing.T) { - SetupFS() - - //Wrong db name -> Expect error - _, err := NewTestToolConfig(server.URL+"/api/v1", "Fail", []string{"path"}) - test_utils.ExpectError(t, err, "NewTestToolConfig did not fail on wrong dbName") - - //Wrong db name -> Expect error - _, err = NewTestToolConfig("invalid", "sTest", []string{"path"}) - test_utils.ExpectError(t, err, "NewTestToolConfig did not fail on wrong server url") - - //Wrong db name -> Expect error - _, err = NewTestToolConfig(server.URL+"/api/v1", "sTest", []string{"path"}) - test_utils.CheckError(t, err, fmt.Sprintf("NewTestToolConfig failed with right dbName: %s", err)) - -} - func TestTestToolConfig_ExtractTestDirectories(t *testing.T) { SetupFS() diff --git a/go.mod b/go.mod index 147a737..6f199ff 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/programmfabrik/fylr-apitest require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/programmfabrik/go-test-utils v0.0.0-20190123092434-88356e86fb35 github.com/sirupsen/logrus v1.3.0 github.com/spf13/afero v1.1.2 github.com/spf13/cobra v0.0.3 diff --git a/lib/api/build_policies_test.go b/lib/api/build_policies_test.go index 7bf8f1c..a0de780 100644 --- a/lib/api/build_policies_test.go +++ b/lib/api/build_policies_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/programmfabrik/fylr-apitest/lib/filesystem" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" + "github.com/programmfabrik/go-test-utils" "github.com/spf13/afero" ) @@ -27,7 +27,7 @@ func TestBuildMultipart(t *testing.T) { BodyType: "multipart", } - httpRequest, err := testRequest.buildHttpRequest("some_interface", "some_token") + httpRequest, err := testRequest.buildHttpRequest() test_utils.CheckError(t, err, "error building multipart request") testReader, err := httpRequest.MultipartReader() diff --git a/lib/api/datastore_test.go b/lib/api/datastore_test.go index 1227712..11f1ccc 100644 --- a/lib/api/datastore_test.go +++ b/lib/api/datastore_test.go @@ -3,7 +3,7 @@ package api import ( "testing" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" + "github.com/programmfabrik/go-test-utils" ) func TestDataStore_Get(t *testing.T) { diff --git a/lib/api/request.go b/lib/api/request.go index 887a5b4..0f2b1ac 100644 --- a/lib/api/request.go +++ b/lib/api/request.go @@ -2,14 +2,26 @@ package api import ( "fmt" + "github.com/programmfabrik/fylr-apitest/lib/logging" "github.com/programmfabrik/fylr-apitest/lib/util" + "time" + "io" "net/http" "net/http/httputil" ) +var c http.Client + +func init() { + c = http.Client{ + Timeout: time.Second * 10, + } +} + type Request struct { Endpoint string `yaml:"endpoint" json:"endpoint"` + ServerURL string `yaml:"serverurl" json:"serverurl"` Method string `yaml:"method" json:"method"` QueryParams map[string]interface{} `yaml:"query_params" json:"query_params"` Headers map[string]string `yaml:"header" json:"header"` @@ -19,9 +31,10 @@ type Request struct { buildPolicy func(Request) (additionalHeaders map[string]string, body io.Reader, err error) DoNotStore bool ManifestDir string + DataStore *Datastore } -func (request Request) buildHttpRequest(serverUrl string, token string) (res *http.Request, err error) { +func (request Request) buildHttpRequest() (res *http.Request, err error) { if request.buildPolicy == nil { //Set Build policy switch request.BodyType { @@ -34,7 +47,7 @@ func (request Request) buildHttpRequest(serverUrl string, token string) (res *ht } } //Render Request Url - requestUrl := fmt.Sprintf("%s/%s", serverUrl, request.Endpoint) + requestUrl := fmt.Sprintf("%s/%s", request.ServerURL, request.Endpoint) additionalHeaders, body, err := request.buildPolicy(request) if err != nil { @@ -59,15 +72,32 @@ func (request Request) buildHttpRequest(serverUrl string, token string) (res *ht res.Header.Add(key, val) } - additionalHeaders["x-easydb-token"] = token for key, val := range additionalHeaders { res.Header.Add(key, val) } + + //Get own headers from datastore + if request.DataStore != nil { + headersInt, err := request.DataStore.Get("httpHeaders") + if err != nil { + return nil, fmt.Errorf("Could not get 'httpHeaders' from Datastore: %s", err) + } + ownHeaders, ok := headersInt.(map[string]interface{}) + if ok { + for key, val := range ownHeaders { + valString, ok := val.(string) + if ok { + res.Header.Add(key, valString) + } + } + } + } + return res, nil } -func (request Request) ToString(session Session) (res string) { - httpRequest, err := request.buildHttpRequest(session.serverUrl, session.token) +func (request Request) ToString() (res string) { + httpRequest, err := request.buildHttpRequest() if err != nil { return fmt.Sprintf("could not build httpRequest: %s", err) } @@ -84,3 +114,24 @@ func (request Request) ToString(session Session) (res string) { } return string(resBytes) } + +func (request Request) Send() (response Response, err error) { + logging.DebugWithVerbosityf(logging.V2, "request: %s", request.ToString()) + httpRequest, err := request.buildHttpRequest() + if err != nil { + return response, err + } + + httpResponse, err := c.Do(httpRequest) + if err != nil { + return response, err + } + + response, err = NewResponse(httpResponse.StatusCode, httpResponse.Header, httpResponse.Body) + if err != nil { + return response, fmt.Errorf("error constructing response from http response") + } + logging.DebugWithVerbosityf(logging.V2, "response: %s", response.ToString()) + + return response, err +} diff --git a/lib/api/request_test.go b/lib/api/request_test.go index 014ef0d..eece885 100644 --- a/lib/api/request_test.go +++ b/lib/api/request_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" + "github.com/programmfabrik/go-test-utils" ) func TestRequestBuildHttp(t *testing.T) { @@ -17,6 +17,7 @@ func TestRequestBuildHttp(t *testing.T) { QueryParams: map[string]interface{}{ "query_param": "value", }, + ServerURL: "serverUrl", } request.buildPolicy = func(request Request) (ah map[string]string, b io.Reader, err error) { ah = make(map[string]string) @@ -24,10 +25,9 @@ func TestRequestBuildHttp(t *testing.T) { b = strings.NewReader("mock_body") return ah, b, nil } - httpRequest, err := request.buildHttpRequest("serverUrl", "token") + httpRequest, err := request.buildHttpRequest() test_utils.CheckError(t, err, fmt.Sprintf("error building http-request: %s", err)) test_utils.AssertStringEquals(t, httpRequest.Header.Get("mock-header"), "application/mock") - test_utils.AssertStringEquals(t, httpRequest.Header.Get("x-easydb-token"), "token") assertBody, err := ioutil.ReadAll(httpRequest.Body) test_utils.CheckError(t, err, fmt.Sprintf("error reading http-request body: %s", err)) diff --git a/lib/api/response_test.go b/lib/api/response_test.go index b168bc2..014f3bb 100644 --- a/lib/api/response_test.go +++ b/lib/api/response_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" + "github.com/programmfabrik/go-test-utils" ) func TestResponse_ToGenericJson(t *testing.T) { diff --git a/lib/api/session.go b/lib/api/session.go deleted file mode 100644 index 9ab5486..0000000 --- a/lib/api/session.go +++ /dev/null @@ -1,244 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/programmfabrik/fylr-apitest/lib/logging" -) - -/* -Performs API Calls in the context of the TestSuite -*/ - -type Session struct { - Store *Datastore - client *http.Client - serverUrl string - token string - MaxEventId int -} - -type SessionAuthentication struct { - Login string `json:"login"` - Password string `json:"password"` - Method string `json:"method"` - StoreResponse map[string]string `json:"store_response_qjson"` // store qjson parsed response in datastore -} - -func NewSession( - serverUrl string, - client *http.Client, - auth *SessionAuthentication, - store *Datastore, -) (session Session, err error) { - - session.client = client - session.serverUrl = serverUrl - session.Store = store - - response, err := session.SendSessionRequest() - if err != nil { - return session, fmt.Errorf("error GET-ing session endpoint: %s", err) - } - - session.token = response.Token - if auth != nil { - if err = session.login(*auth); err != nil { - return session, fmt.Errorf("error logging session in: %s", err) - } - } - return session, nil -} - -// login gets you an authenticated session token -// The supported authentication methods are: -// “easydb” (default): authenticate easydb user -// login: ist the username -// password: is the users password -// “email”: authenticate an easydb user of type “email” -// login: its email -// password: collection UUID -// “anonymous”: a virtual user will be created -// login: empty -// password: empty -// “task”: authenticate easydb to perform a task -// login: email-adress -// password: authentication token for the process -// “collection”: authenticate an easydb user of type “collection” -// login: collection UUID -// password: collection secret -func (session *Session) login(auth SessionAuthentication) (err error) { - - response, err := session.SendSessionAuthenticateRequest(auth) - if err != nil { - return fmt.Errorf("error performing authentication request: %s", err) - } - - session.MaxEventId = response.CurMaxEventId - return nil -} - -func (session *Session) SendRequest(request Request) (response Response, err error) { - logging.DebugWithVerbosityf(logging.V2, "request: %s", request.ToString(*session)) - httpRequest, err := request.buildHttpRequest(session.serverUrl, session.token) - if err != nil { - return response, err - } - - httpResponse, err := session.client.Do(httpRequest) - if err != nil { - return response, err - } - - response, err = NewResponse(httpResponse.StatusCode, httpResponse.Header, httpResponse.Body) - if err != nil { - return response, fmt.Errorf("error constructing response from http response") - } - logging.DebugWithVerbosityf(logging.V2, "response: %s", response.ToString()) - - return response, err -} - -/* -Convenience methods from within the application -*/ - -//Body of GET /settings response -//https://docs.easydb.de/en/technical/api/settings/settings.html -type settingsBody struct { - Name string `json:"name"` - Api int `json:"api"` - ServerVersion int `json:"server_version"` - UserSchema int `json:"user-schema"` - Solution string `json:"solution"` - DbName string `json:"db-name"` - ExternalEasUrl string `json:"external_eas_url"` - //startup time left out - //server time left out -} - -func (session *Session) SendSettingsRequest() (res settingsBody, err error) { - request := Request{ - Endpoint: "settings", - Method: "GET", - buildPolicy: buildRegular, - DoNotStore: true, - } - resp, err := session.SendRequest(request) - if err != nil { - return res, err - } - - if resp.statusCode != 200 { - apiErr := apiError{} - if err = resp.marshalBodyInto(&apiErr); err != nil { - return res, err - } - apiErr.Statuscode = resp.statusCode - - return res, apiErr - } - - if err = resp.marshalBodyInto(&res); err != nil { - return res, err - } - return res, nil -} - -//Body of GET /session response -//https://docs.easydb.de/en/technical/types/session/session.html -type sessionBody struct { - Token string `json:"token"` - //... -} - -type apiError struct { - Statuscode int `json:"statuscode"` - Code string `json:"code"` -} - -func (apiErr apiError) Error() string { - jsonErr, err := json.Marshal(apiErr) - if err != nil { - return err.Error() - } - return string(jsonErr) -} - -func (session *Session) SendSessionRequest() (res sessionBody, err error) { - request := Request{ - Endpoint: "session", - Method: "GET", - buildPolicy: buildRegular, - DoNotStore: true, - } - resp, err := session.SendRequest(request) - if err != nil { - return res, err - } - - if resp.statusCode != 200 { - apiErr := apiError{} - if err = resp.marshalBodyInto(&apiErr); err != nil { - return res, err - } - apiErr.Statuscode = resp.statusCode - - return res, apiErr - } - - if err = resp.marshalBodyInto(&res); err != nil { - return res, err - } - return res, nil -} - -//Body of POST /session/authenticate response -//https://docs.easydb.de/en/technical/api/session/session.html -type sessionAuthenticateBody struct { - CurMaxEventId int `json:"current_max_event_id"` -} - -func (session *Session) SendSessionAuthenticateRequest(auth SessionAuthentication) (res sessionAuthenticateBody, err error) { - request := Request{ - Endpoint: "session/authenticate", - Method: "POST", - Headers: map[string]string{ - "token": session.token, - }, - Body: map[string]string{ - "login": auth.Login, - "password": auth.Password, - "method": auth.Method, - }, - buildPolicy: buildUrlencoded, - DoNotStore: true, - } - - resp, err := session.SendRequest(request) - if err != nil { - return res, err - } - if resp.statusCode != 200 { - apiErr := apiError{} - if err = resp.marshalBodyInto(&apiErr); err != nil { - return res, err - } - apiErr.Statuscode = resp.statusCode - - return res, apiErr - } - - err = session.Store.SetWithQjson(resp, auth.StoreResponse) - if err != nil { - err = fmt.Errorf("unable to store response of session %s", err) - return - } - - if err = resp.marshalBodyInto(&res); err != nil { - return res, err - } - return res, nil -} diff --git a/lib/api/session_test.go b/lib/api/session_test.go deleted file mode 100644 index 8bc69d4..0000000 --- a/lib/api/session_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - "strings" - "testing" - - "github.com/programmfabrik/fylr-apitest/lib/test_utils" -) - -func TestNewSessionSucceeds(t *testing.T) { - server := test_utils.NewTestServer(test_utils.Routes{ - "/api/v1/session": func(w *http.ResponseWriter, r *http.Request) { - (*w).Write([]byte("{\"token\": \"mock\"}")) - }, - }) - client := server.Client() - defer server.Close() - session, err := NewSession(server.URL+"/api/v1", client, nil, &Datastore{}) - test_utils.CheckError(t, err, fmt.Sprintf("error creating session: %s", err)) - test_utils.AssertStringEquals(t, session.token, "mock") -} - -func TestNewSessionFails(t *testing.T) { - server := test_utils.NewTestServer(test_utils.Routes{ - "/api/v1/session": func(w *http.ResponseWriter, r *http.Request) { - (*w).WriteHeader(http.StatusInternalServerError) - (*w).Write([]byte("{\"code\": \"error.server.fail\"}")) - }, - }) - client := server.Client() - defer server.Close() - _, err := NewSession(server.URL+"/api/v1", client, nil, &Datastore{}) - if err == nil || !strings.Contains(err.Error(), "error.server.fail") { - t.Errorf("expected error to contain: 'error.server.fail', got %s", err) - } -} - -func TestSessionLoginSuccess(t *testing.T) { - server := test_utils.NewTestServer(test_utils.Routes{ - "/api/v1/session": func(w *http.ResponseWriter, r *http.Request) { - (*w).Write([]byte("{\"token\": \"mock\"}")) - }, - "/api/v1/session/authenticate": func(w *http.ResponseWriter, r *http.Request) { - (*w).Write([]byte("{\"current_max_event_id\": 2391}")) - }, - }) - client := server.Client() - defer server.Close() - auth := &SessionAuthentication{} - session, err := NewSession(server.URL+"/api/v1", client, auth, &Datastore{}) - test_utils.CheckError(t, err, fmt.Sprintf("error creating session: %s", err)) - test_utils.AssertStringEquals(t, session.token, "mock") - test_utils.AssertIntEquals(t, session.MaxEventId, 2391) -} - -func TestSessionLoginFailsNotAuthenticated(t *testing.T) { - server := test_utils.NewTestServer(test_utils.Routes{ - "/api/v1/session": func(w *http.ResponseWriter, r *http.Request) { - (*w).Write([]byte("{\"token\": \"mock\"}")) - }, - "/api/v1/session/authenticate": func(w *http.ResponseWriter, r *http.Request) { - (*w).WriteHeader(400) - (*w).Write([]byte("{\"code\": \"error.something.failed\"}")) - }, - }) - client := server.Client() - defer server.Close() - auth := &SessionAuthentication{} - _, err := NewSession(server.URL+"/api/v1", client, auth, &Datastore{}) - if err == nil || !strings.Contains(err.Error(), "error.something.failed") { - t.Errorf("expected error to contain: 'error.something.failed', got %s", err) - } -} - -func TestSessionSettingsRequest(t *testing.T) { - server := test_utils.NewTestServer(test_utils.Routes{ - "/api/v1/settings": func(w *http.ResponseWriter, r *http.Request) { - (*w).Write([]byte("{\"db-name\": \"mock\"}")) - }, - }) - client := server.Client() - defer server.Close() - session := Session{Store: nil, client: client, serverUrl: server.URL + "/api/v1"} - settingsResponse, err := session.SendSettingsRequest() - test_utils.CheckError(t, err, fmt.Sprintf("error sending settings request: %s", err)) - test_utils.AssertStringEquals(t, settingsResponse.DbName, "mock") -} diff --git a/lib/cjson/cjson_test.go b/lib/cjson/cjson_test.go index 40124fd..e1b22cb 100644 --- a/lib/cjson/cjson_test.go +++ b/lib/cjson/cjson_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/programmfabrik/fylr-apitest/lib/compare" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" "github.com/programmfabrik/fylr-apitest/lib/util" + "github.com/programmfabrik/go-test-utils" ) func TestLineAndCharacter(t *testing.T) { @@ -69,29 +69,35 @@ func TestRealWorldJsonError(t *testing.T) { `{ "hallo":2, }`, - fmt.Errorf(`Cannot parse JSON '{ -"hallo":2, -}' schema due to a syntax error at line 3, character 1: invalid character '}' looking for beginning of object key string`), + fmt.Errorf(`Cannot parse JSON ' +1 { +2 "hallo":2, +3 } +' schema due to a syntax error at line 3, character 1: invalid character '}' looking for beginning of object key string`), }, { `{ "hallo":2, welt:1 }`, - fmt.Errorf(`Cannot parse JSON '{ -"hallo":2, -welt:1 -}' schema due to a syntax error at line 3, character 1: invalid character 'w' looking for beginning of object key string`), + fmt.Errorf(`Cannot parse JSON ' +1 { +2 "hallo":2, +3 welt:1 +4 } +' schema due to a syntax error at line 3, character 1: invalid character 'w' looking for beginning of object key string`), }, { `{ "hallo": 2, "welt": "string }`, - fmt.Errorf(`Cannot parse JSON '{ -"hallo": 2, -"welt": "string -}' schema due to a syntax error at line 4, character 0: invalid character '\n' in string literal`), + fmt.Errorf(`Cannot parse JSON ' +1 { +2 "hallo": 2, +3 "welt": "string +4 } +' schema due to a syntax error at line 4, character 0: invalid character '\n' in string literal`), }, } @@ -117,38 +123,6 @@ func TestRemoveComments(t *testing.T) { "hallo": float64(2), }, }, - { - `{ -"hallo":2 //as -}`, - util.JsonObject{ - "hallo": float64(2), - }, - }, - { - `{ -"hallo":2 //as -## line 2 - -#line2 -}`, - util.JsonObject{ - "hallo": float64(2), - }, - }, - { - `{ -"hallo":2, //as -## line 2 - -#line2 -"hey":"ha" -}`, - util.JsonObject{ - "hallo": float64(2), - "hey": "ha", - }, - }, } for _, v := range testCases { @@ -179,8 +153,10 @@ func TestCJSONUnmarshalSyntaxErr(t *testing.T) { { cjsonString: `{"hallo":3 "fail":"s"}`, - eError: fmt.Errorf(`Cannot parse JSON '{"hallo":3 -"fail":"s"}' schema due to a syntax error at line 2, character 1: invalid character '"' after object key:value pair`), + eError: fmt.Errorf(`Cannot parse JSON ' +1 {"hallo":3 +2 "fail":"s"} +' schema due to a syntax error at line 2, character 1: invalid character '"' after object key:value pair`), }, { cjsonString: `{"hallo":3, @@ -189,12 +165,14 @@ func TestCJSONUnmarshalSyntaxErr(t *testing.T) { "hey":"e } }`, - eError: fmt.Errorf(`Cannot parse JSON '{"hallo":3, -"fail":"s", -"simon":{ - "hey":"e -} -}' schema due to a syntax error at line 5, character 0: invalid character '\n' in string literal`), + eError: fmt.Errorf(`Cannot parse JSON ' +1 {"hallo":3, +2 "fail":"s", +3 "simon":{ +4 "hey":"e +5 } +6 } +' schema due to a syntax error at line 5, character 0: invalid character '\n' in string literal`), }, } @@ -231,8 +209,12 @@ func TestCJSONUnmarshalTypeErr(t *testing.T) { t, oErr, []error{ - fmt.Errorf("In JSON '%s', the type 'number' cannot be converted into the Go 'string' type on struct '', field ''. See input file line 1, character 9", cjsonString), - fmt.Errorf("In JSON '%s', the type 'number' cannot be converted into the Go 'string' type on struct 'expectedStructure', field 'name'. See input file line 1, character 9", cjsonString), + fmt.Errorf(`In JSON ' +1 %s +', the type 'number' cannot be converted into the Go 'string' type on struct '', field ''. See input file line 1, character 9`, cjsonString), + fmt.Errorf(`In JSON ' +1 %s +', the type 'number' cannot be converted into the Go 'string' type on struct 'expectedStructure', field 'name'. See input file line 1, character 9`, cjsonString), }, ) } diff --git a/lib/compare/comparison_functions_test.go b/lib/compare/comparison_functions_test.go index 7f681d3..8db94a1 100644 --- a/lib/compare/comparison_functions_test.go +++ b/lib/compare/comparison_functions_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" + "github.com/programmfabrik/go-test-utils" "github.com/programmfabrik/fylr-apitest/lib/util" ) diff --git a/lib/csv/csv.go b/lib/csv/csv.go index 2883650..2c38bf7 100644 --- a/lib/csv/csv.go +++ b/lib/csv/csv.go @@ -249,4 +249,6 @@ func getTyped(value, format string) (interface{}, error) { return nil, fmt.Errorf("Given format '%s' not supported for csv usage", format) } + return nil, fmt.Errorf("Given format '%s' not supported for csv usage", format) + } diff --git a/lib/template/template_funcs_test.go b/lib/template/template_funcs_test.go index 507907e..8df07d6 100644 --- a/lib/template/template_funcs_test.go +++ b/lib/template/template_funcs_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" + "github.com/programmfabrik/go-test-utils" ) func Test_QJson_String(t *testing.T) { diff --git a/lib/template/template_loader_test.go b/lib/template/template_loader_test.go index 38527bb..0653527 100644 --- a/lib/template/template_loader_test.go +++ b/lib/template/template_loader_test.go @@ -9,7 +9,7 @@ import ( "github.com/programmfabrik/fylr-apitest/lib/api" "github.com/programmfabrik/fylr-apitest/lib/filesystem" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" + "github.com/programmfabrik/go-test-utils" "github.com/spf13/afero" ) @@ -144,7 +144,7 @@ int64,string,"string,array","int64,array" ,hans,, 1,simon,"simon,jo nas,ste -fan","21,24,12"`, ``, fmt.Errorf(`error executing body template: template: tmpl:1:3: executing "tmpl" at : error calling file_csv: Only one row is allowed for type 'string,array'`)}, +fan","21,24,12"`, ``, fmt.Errorf(`error executing body template: template: tmpl:1:3: executing "tmpl" at : error calling file_csv: 'somefile.json' Only one row is allowed for type 'string,array'`)}, {`id,name,friends,ages @@ -185,11 +185,11 @@ int64,string,s,"string,array" ,,, #,hans,, 1,simon,LALALALA,"simon,""jo -nas"",""a,b""","21,24,12"`, ``, fmt.Errorf(`error executing body template: template: tmpl:1:3: executing "tmpl" at : error calling file_csv: 's' is no valid format`)}, +nas"",""a,b""","21,24,12"`, ``, fmt.Errorf(`error executing body template: template: tmpl:1:3: executing "tmpl" at : error calling file_csv: 'somefile.json' 's' is no valid format`)}, {`id,name,,ages int64,string,"string,array","int64,array"`, `[]`, nil}, {`id,name,friends,ages -int64,string,"stringer,array","int64,array"`, ``, fmt.Errorf(`error executing body template: template: tmpl:1:3: executing "tmpl" at : error calling file_csv: 'stringer,array' is no valid format`)}, +int64,string,"stringer,array","int64,array"`, ``, fmt.Errorf(`error executing body template: template: tmpl:1:3: executing "tmpl" at : error calling file_csv: 'somefile.json' 'stringer,array' is no valid format`)}, } for i, testCase := range testCases { t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { diff --git a/lib/test_utils/assert.go b/lib/test_utils/assert.go deleted file mode 100644 index 04db347..0000000 --- a/lib/test_utils/assert.go +++ /dev/null @@ -1,184 +0,0 @@ -package test_utils - -import ( - "strings" - "testing" -) - -func AssertStringEquals(t *testing.T, have, want string) { - if have != want { - t.Errorf("Have '%s' != '%s' Want", have, want) - } -} - -func AssertIntEquals(t *testing.T, have, want int) { - if have != want { - t.Errorf("Have '%d' != '%d' Want", have, want) - } -} - -func AssertErrorEquals(t *testing.T, have, want error) { - if want == nil && have != nil { - t.Errorf("Have '%v' != '%v' Want", have, want) - return - } - if want != nil && have == nil { - t.Errorf("Have '%v' != '%v' Want", have, want) - return - } - - if want == nil && have == nil { - return - } - - if have.Error() != want.Error() { - t.Errorf("Have '%v' != '%v' Want", have, want) - return - } -} - -func AsserErrorEqualsAny(t *testing.T, have error, want []error) { - if want == nil && have != nil { - t.Errorf("Have '%v' != '%v' Want", have, want) - return - } - if want != nil && have == nil { - t.Errorf("Have '%v' != '%v' Want", have, want) - return - } - - if want == nil && have == nil { - return - } - for _, v := range want { - if have.Error() == v.Error() { - return - } - } - - t.Errorf("'%v' not in '%v'", have, want) -} - -func AssertErrorContains(t *testing.T, have, want error) { - if want == nil && have != nil { - t.Errorf("'%v' != '%v'", have, want) - return - } - if want != nil && have == nil { - t.Errorf("'%v' != '%v'", have, want) - return - } - - if want == nil && have == nil { - return - } - - if !strings.Contains(have.Error(), want.Error()) { - t.Errorf("'%v' was not found in '%v'", want, have) - return - } -} - -func CheckError(t *testing.T, err error, errorMessage string) { - if err != nil { - t.Error(errorMessage) - } -} - -func ExpectError(t *testing.T, err error, errorMessage string) { - if err == nil { - t.Error(errorMessage) - } -} - -func AssertStringContainsSubstringsInOrder(t *testing.T, body string, expectedStrings []string) { - cI := 0 - print := false - for _, v := range expectedStrings { - i := strings.Index(body, v) - if i < cI { - t.Errorf("Wrong order in string. '%s' is at the wrong position", v) - print = true - } else { - cI = i - } - } - - if print { - t.Log(body) - } -} - -func AssertStringContainsSubstringsNoOrder(t *testing.T, body string, expectedStrings []string) { - for _, v := range expectedStrings { - if !strings.Contains(body, v) { - t.Errorf("'%s' not found.", v) - } - } -} - -func AssertStringContainsNoneOfTheSubstrings(t *testing.T, body string, notExpectedLogEntries []string) { - print := false - for _, v := range notExpectedLogEntries { - if strings.Contains(body, v) { - t.Errorf("We did not expect '%s' but found it in '%s'", v, body) - print = true - } - - } - - if print { - t.Log(body) - } -} - -// AssertMapsEqual checks if two maps have the exact same content -// Attention: This function changes the value of the first map! -func AssertMapsEqual(t *testing.T, got, want map[string]interface{}) { - - for k, v := range want { - if got[k] != v { - t.Errorf("[%s] Got '%v' != '%v' Want", k, got[k], v) - } else { - delete(got, k) - } - } - - for k, v := range got { - t.Errorf("[%s] Got '%v' != '' Want", k, v) - - } -} -func AssertStringArraysEqualNoOrder(t *testing.T, have, want []string) { - wantInner := make([]string, len(want)) - copy(wantInner, want) - - if want == nil && have != nil { - t.Errorf("Have '%v' != '%v' Want", have, want) - return - } - if want != nil && have == nil { - t.Errorf("Have '%v' != '%v' Want", have, want) - return - } - - if want == nil && have == nil { - return - } - - for _, v := range have { - for ik, iv := range wantInner { - if v == iv { - wantInner = append(wantInner[:ik], wantInner[ik+1:]...) - break - } - } - } - - if len(wantInner) > 0 { - for _, v := range wantInner { - t.Errorf("'%s' not found", v) - } - } - -} diff --git a/lib/test_utils/server.go b/lib/test_utils/server.go deleted file mode 100644 index f3add54..0000000 --- a/lib/test_utils/server.go +++ /dev/null @@ -1,26 +0,0 @@ -package test_utils - -import ( - "net/http" - "net/http/httptest" -) - -type handle func(*http.ResponseWriter, *http.Request) -type Routes map[string]handle - -func handleWithRoute(w http.ResponseWriter, r *http.Request, routes Routes) { - path := r.URL.Path - handle, ok := routes[path] - if !ok { - w.WriteHeader(500) - return - } - handle(&w, r) -} - -func NewTestServer(routes Routes) *httptest.Server { - routingHandle := func(w http.ResponseWriter, r *http.Request) { - handleWithRoute(w, r, routes) - } - return httptest.NewServer(http.HandlerFunc(routingHandle)) -} diff --git a/lib/test_utils/test_utils.go b/lib/test_utils/test_utils.go index f9db7f9..1557597 100644 --- a/lib/test_utils/test_utils.go +++ b/lib/test_utils/test_utils.go @@ -3,12 +3,13 @@ package test_utils import ( "bytes" "fmt" + "github.com/programmfabrik/go-test-utils" "net/http" "regexp" "strings" ) -var TestServer = NewTestServer(Routes{ +var TestServer = test_utils.NewTestServer(test_utils.Routes{ "/api/v1/session": func(w *http.ResponseWriter, r *http.Request) { (*w).Write([]byte("{\"token\": \"mock\"}")) }, diff --git a/main.go b/main.go index a7aed8e..88b2b53 100644 --- a/main.go +++ b/main.go @@ -10,23 +10,20 @@ import ( "github.com/programmfabrik/fylr-apitest/lib/report" "io/ioutil" "log" - "net/http" "path/filepath" - "os" "github.com/spf13/cobra" "github.com/spf13/viper" - + "os" ) var ( - reportFormat, reportFile string - verbosity int - noRequirements bool + reportFormat, reportFile string + verbosity int + noRequirements bool rootDirectorys, singleTests []string ) - func init() { //Configure all the flags that fylr-apitest offers TestCMD.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./fylr.yml", "config file") @@ -61,7 +58,6 @@ verbosities >= 0 automatically set the global loglevel to debug`) &reportFormat, "report-format", "", "Defines how the report statements should be saved. [junit/json]") - //Bind the flags to overwrite the yml config if they are set viper.BindPFlag("log.console.enable", TestCMD.PersistentFlags().Lookup("log-console-enable")) viper.BindPFlag("log.console.level", TestCMD.PersistentFlags().Lookup("log-console-level")) @@ -69,141 +65,138 @@ verbosities >= 0 automatically set the global loglevel to debug`) viper.BindPFlag("apitest.report.format", TestCMD.PersistentFlags().Lookup("report-format")) } - var TestCMD = &cobra.Command{ Args: cobra.MaximumNArgs(0), PersistentPreRun: setup, - Use: "fylr apitest", - Short: "flyr Apitester lets you define API tests on the go", - Long: `A fast and flexible API testing tool. Helping you to define API tests on the go`, - Run: func(cmd *cobra.Command, args []string) { + Use: "fylr apitest", + Short: "flyr Apitester lets you define API tests on the go", + Long: `A fast and flexible API testing tool. Helping you to define API tests on the go`, + Run: runApiTests, +} - noRequirements = (cmd.Flag("no-requirements").Value.String() == "true") +func main() { + err := TestCMD.Execute() + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} - //Check if paths are valid +var cfgFile string - for _, rootDirectory := range rootDirectorys { - if _, err := os.Stat(rootDirectory); rootDirectory != "." && os.IsNotExist(err) { - logging.Errorf("The path '%s' for the test folders is not valid", rootDirectory) - os.Exit(1) - } - } - for _, singleTest := range singleTests { - if _, err := os.Stat(singleTest); singleTest != "" && os.IsNotExist(err) { - logging.Errorf("The path '%s' for the single test is not valid", singleTest) - os.Exit(1) - } - } +func setup(ccmd *cobra.Command, args []string) { + //Load yml config + LoadConfig(cfgFile) + + //Setup logging + if err := logging.ConfigureLogging( + viper.GetBool("log.console.enable"), + viper.GetString("log.console.level")); err != nil { + log.Fatalf("error configuring logging: %s", err) + } +} - if err := logging.InitApiTestLogging(verbosity); err != nil { - logging.Errorf("Could not configure verbosity of apitest logging") +func runApiTests(cmd *cobra.Command, args []string) { + + noRequirements = (cmd.Flag("no-requirements").Value.String() == "true") + + //Check if paths are valid + + for _, rootDirectory := range rootDirectorys { + if _, err := os.Stat(rootDirectory); rootDirectory != "." && os.IsNotExist(err) { + logging.Errorf("The path '%s' for the test folders is not valid", rootDirectory) os.Exit(1) } + } + for _, singleTest := range singleTests { + if _, err := os.Stat(singleTest); singleTest != "" && os.IsNotExist(err) { + logging.Errorf("The path '%s' for the single test is not valid", singleTest) + os.Exit(1) + } + } + + if err := logging.InitApiTestLogging(verbosity); err != nil { + logging.Errorf("Could not configure verbosity of apitest logging") + os.Exit(1) + } + + serverUrl := FylrConfig.Apitest.Server + dbName := FylrConfig.Apitest.DBName + reportFormat = FylrConfig.Apitest.Report.Format + reportFile = FylrConfig.Apitest.Report.File + + //Save the config into TestToolConfig + testToolConfig, err := NewTestToolConfig(serverUrl, dbName, rootDirectorys) + if err != nil { + logging.Error(err) + os.Exit(1) + } - serverUrl := FylrConfig.Apitest.Server - dbName := FylrConfig.Apitest.DBName - reportFormat = FylrConfig.Apitest.Report.Format - reportFile = FylrConfig.Apitest.Report.File + datastore := api.NewStore() + for k, v := range FylrConfig.Apitest.StoreInit { + datastore.Set(k, v) + logging.Infof("Add Init value for datastore Key: '%s', Value: '%v'", k, v) + } - //Save the config into TestToolConfig - testToolConfig, err := NewTestToolConfig(serverUrl, dbName, rootDirectorys) + //Actually run the tests + //Run test function + runSingleTest := func(manifestPath string, r *report.Report) { + suite, err := NewTestSuite( + testToolConfig, + manifestPath, + r, + !noRequirements, + datastore, + 0, + ) if err != nil { logging.Error(err) os.Exit(1) } - datastore := api.NewStore() - for k, v := range FylrConfig.Apitest.StoreInit { - datastore.Set(k, v) - logging.Infof("Add Init value for datastore Key: '%s', Value: '%v'", k, v) - } + suite.Run() + } - //Actually run the tests - //Run test function - runSingleTest := func(manifestPath string, r *report.Report) { - suite, err := NewTestSuite( - &http.Client{}, - testToolConfig, - manifestPath, - r, - !noRequirements, - datastore, - 0, - ) - if err != nil { - logging.Error(err) - os.Exit(1) - } - - suite.Run() - } + r := report.NewReport() - r := report.NewReport() - - //Decide if run only one test - if len(singleTests) > 0 { - for _, singleTest := range singleTests { - absManifestPath, _ := filepath.Abs(singleTest) - runSingleTest(absManifestPath, r) - } - } else { - for _, singlerootDirectory := range testToolConfig.TestDirectories { - manifestPath := filepath.Join(singlerootDirectory, "manifest.json") - absManifestPath, _ := filepath.Abs(manifestPath) - runSingleTest(absManifestPath, r) - } + //Decide if run only one test + if len(singleTests) > 0 { + for _, singleTest := range singleTests { + absManifestPath, _ := filepath.Abs(singleTest) + runSingleTest(absManifestPath, r) } - - //Create report - if reportFile != "" { - var parsingFunction func(baseResult *report.ReportElement) []byte - switch reportFormat { - case "junit": - parsingFunction = report.ParseJUnitResult - case "json": - parsingFunction = report.ParseJSONResult - default: - logging.Errorf( - "Given report format '%s' not supported. Saving report '%s' as json", - reportFormat, - reportFile) - - parsingFunction = report.ParseJSONResult - } - - err = ioutil.WriteFile(reportFile, r.GetTestResult(parsingFunction), 0644) - if err != nil { - fmt.Println("Could not save into file: ", err) - } + } else { + for _, singlerootDirectory := range testToolConfig.TestDirectories { + manifestPath := filepath.Join(singlerootDirectory, "manifest.json") + absManifestPath, _ := filepath.Abs(manifestPath) + runSingleTest(absManifestPath, r) } + } - if r.DidFail() { - os.Exit(1) + //Create report + if reportFile != "" { + var parsingFunction func(baseResult *report.ReportElement) []byte + switch reportFormat { + case "junit": + parsingFunction = report.ParseJUnitResult + case "json": + parsingFunction = report.ParseJSONResult + default: + logging.Errorf( + "Given report format '%s' not supported. Saving report '%s' as json", + reportFormat, + reportFile) + + parsingFunction = report.ParseJSONResult } - }, -} - -func main() { - err := TestCMD.Execute() - if err != nil { - fmt.Println(err) - os.Exit(1) + err = ioutil.WriteFile(reportFile, r.GetTestResult(parsingFunction), 0644) + if err != nil { + fmt.Println("Could not save into file: ", err) + } } -} - - -var cfgFile string - -func setup(ccmd *cobra.Command, args []string) { - //Load yml config - LoadConfig(cfgFile) - //Setup logging - if err := logging.ConfigureLogging( - viper.GetBool("log.console.enable"), - viper.GetString("log.console.level")); err != nil{ - log.Fatalf("error configuring logging: %s", err) + if r.DidFail() { + os.Exit(1) } } - From 8c58c8b6ee05b2b94014e16716c3c86779474f7b Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 6 Feb 2019 11:25:59 +0100 Subject: [PATCH 04/17] request.go: Add HeaderFromStore so that we can set header information from the datatstore (See #52174) --- lib/api/request.go | 57 ++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/lib/api/request.go b/lib/api/request.go index 0f2b1ac..6ddfb85 100644 --- a/lib/api/request.go +++ b/lib/api/request.go @@ -15,18 +15,19 @@ var c http.Client func init() { c = http.Client{ - Timeout: time.Second * 10, + Timeout: time.Second * 30, } } type Request struct { - Endpoint string `yaml:"endpoint" json:"endpoint"` - ServerURL string `yaml:"serverurl" json:"serverurl"` - Method string `yaml:"method" json:"method"` - QueryParams map[string]interface{} `yaml:"query_params" json:"query_params"` - Headers map[string]string `yaml:"header" json:"header"` - BodyType string `yaml:"body_type" json:"body_type"` - Body util.GenericJson `yaml:"body" json:"body"` + Endpoint string `yaml:"endpoint" json:"endpoint"` + ServerURL string `yaml:"serverurl" json:"serverurl"` + Method string `yaml:"method" json:"method"` + QueryParams map[string]interface{} `yaml:"query_params" json:"query_params"` + Headers map[string]string `yaml:"header" json:"header"` + HeaderFromStore map[string]string `yaml:"header_from_store" json:"header_from_store"` + BodyType string `yaml:"body_type" json:"body_type"` + Body util.GenericJson `yaml:"body" json:"body"` buildPolicy func(Request) (additionalHeaders map[string]string, body io.Reader, err error) DoNotStore bool @@ -68,31 +69,43 @@ func (request Request) buildHttpRequest() (res *http.Request, err error) { } res.URL.RawQuery = q.Encode() - for key, val := range request.Headers { - res.Header.Add(key, val) - } - - for key, val := range additionalHeaders { - res.Header.Add(key, val) - } + for headerName, datastoreKey := range request.HeaderFromStore { + if request.DataStore == nil { + return res, fmt.Errorf("can't get header_from_store as the datastore is nil") + } - //Get own headers from datastore - if request.DataStore != nil { - headersInt, err := request.DataStore.Get("httpHeaders") + headersInt, err := request.DataStore.Get(datastoreKey) if err != nil { - return nil, fmt.Errorf("Could not get 'httpHeaders' from Datastore: %s", err) + return nil, fmt.Errorf("could not get '&s' from Datastore: %s", datastoreKey, err) } - ownHeaders, ok := headersInt.(map[string]interface{}) + + ownHeaders, ok := headersInt.([]interface{}) if ok { - for key, val := range ownHeaders { + for _, val := range ownHeaders { valString, ok := val.(string) if ok { - res.Header.Add(key, valString) + res.Header.Add(headerName, valString) } } + continue + } + + ownHeader, ok := headersInt.(string) + if ok { + res.Header.Add(headerName, ownHeader) + } else { + return nil, fmt.Errorf("could not set header '%s' from Datastore: '%s' is not a string. Got value: '%v'", headerName, datastoreKey, headersInt) } } + for key, val := range request.Headers { + res.Header.Add(key, val) + } + + for key, val := range additionalHeaders { + res.Header.Add(key, val) + } + return res, nil } From 670ff2bd11b29091252241dd5d145aca13530f5c Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 6 Feb 2019 11:35:53 +0100 Subject: [PATCH 05/17] Add documentation for header_from_store --- README.md | 6 ++++++ api_testcase.go | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 760981e..039c308 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,12 @@ Manifest is loaded as **template**, so you can use variables, Go **range** and * "header1":"value", "header2":"value" }, + // With header_from_you set a header to the value of the datastore field + // In this example we set the "Content-Type" header to the value "application/json" + // As "application/json" is stored as string in the datastore on index "contentType" + "header_from_store": { + "Content-Type": "contentType" + } //All the content you want to send in the http body. Is a JSON Object "body":{ "flower":"rose", diff --git a/api_testcase.go b/api_testcase.go index 985e1a2..993dde3 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -59,7 +59,13 @@ func (testCase Case) runAPITestCase() (success bool) { start := time.Now() // Store standard data into datastore - err := testCase.session.Store.SetMap(testCase.Store) + if testCase.dataStore == nil && len(testCase.Store) > 0 { + err := fmt.Errorf("error setting datastore. Datastore is nil") + r.SaveToReportLog(fmt.Sprintf("Error during execution: %s", err)) + logging.Errorf(" [%2d] %s", testCase.index, err) + return false + } + err := testCase.dataStore.SetMap(testCase.Store) if err != nil { err = fmt.Errorf("error setting datastore map:%s", err) r.SaveToReportLog(fmt.Sprintf("Error during execution: %s", err)) @@ -94,7 +100,7 @@ func (testCase Case) runAPITestCase() (success bool) { return } -// checkForBreak Response tests the given response for a so called break response. +// cheRckForBreak Response tests the given response for a so called break response. // If this break response is present it returns a true func (testCase Case) breakResponseIsPresent(request api.Request, response api.Response) (found bool, err error) { From 7b413355968ade73eeb3765933867b23b42b8db4 Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 6 Feb 2019 11:53:51 +0100 Subject: [PATCH 06/17] go vet fixups --- lib/api/request.go | 2 +- lib/cjson/cjson_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/request.go b/lib/api/request.go index 6ddfb85..5eeb943 100644 --- a/lib/api/request.go +++ b/lib/api/request.go @@ -76,7 +76,7 @@ func (request Request) buildHttpRequest() (res *http.Request, err error) { headersInt, err := request.DataStore.Get(datastoreKey) if err != nil { - return nil, fmt.Errorf("could not get '&s' from Datastore: %s", datastoreKey, err) + return nil, fmt.Errorf("could not get '%s' from Datastore: %s", datastoreKey, err) } ownHeaders, ok := headersInt.([]interface{}) diff --git a/lib/cjson/cjson_test.go b/lib/cjson/cjson_test.go index dca4dab..00b199a 100644 --- a/lib/cjson/cjson_test.go +++ b/lib/cjson/cjson_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/programmfabrik/fylr-apitest/lib/compare" - "github.com/programmfabrik/fylr-apitest/lib/test_utils" "github.com/programmfabrik/fylr-apitest/lib/util" + "github.com/programmfabrik/go-test-utils" ) func init() { From f026ee6ffb51b50d25160008e31bd78c7bfa543e Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 6 Feb 2019 14:07:04 +0100 Subject: [PATCH 07/17] Remove multilogger logic and adapt to new logging verbosity settings (See #51147) --- README.md | 8 +- api_testcase.go | 68 +++++---- api_testsuite.go | 22 +-- config.go | 1 + lib/api/build_policies.go | 12 +- lib/api/datastore.go | 4 +- lib/api/request.go | 6 +- lib/api/response.go | 14 +- lib/logging/multilogger.go | 236 -------------------------------- lib/template/template_loader.go | 4 +- main.go | 53 +++---- 11 files changed, 91 insertions(+), 337 deletions(-) delete mode 100644 lib/logging/multilogger.go diff --git a/README.md b/README.md index 039c308..82dd6dc 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ This starts the command with the following default settings: ### Configure logging - `--verbosity 1` or `-v 1`: Set the verbosity of the logger. (Verbosity defines what kind of communication with the server should be shown) Can be realy helpfull to find out why a test fails. - - `-v -1` (default): dont't log any communication with the server - - `-v 0`: log the communication in case a test fails - - `-v 1`: log the communication if it's so defined in the manifest - - `-v 2`: log all server communication + - `-v -1` (default): Only normal test ouput + - `-v 0`: All from '-1' plus failed test responses + - `-v 1`: All from '-1' plus all responses + - `-v 2`: All from '1' plus all requests #### Console logging diff --git a/api_testcase.go b/api_testcase.go index 993dde3..c0dddec 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -10,11 +10,10 @@ import ( "github.com/programmfabrik/fylr-apitest/lib/api" "github.com/programmfabrik/fylr-apitest/lib/compare" - "github.com/programmfabrik/fylr-apitest/lib/logging" - apitestLogging "github.com/programmfabrik/fylr-apitest/lib/logging" "github.com/programmfabrik/fylr-apitest/lib/report" "github.com/programmfabrik/fylr-apitest/lib/template" "github.com/programmfabrik/fylr-apitest/lib/util" + log "github.com/sirupsen/logrus" ) const ( @@ -49,9 +48,9 @@ func (testCase Case) runAPITestCase() (success bool) { r := testCase.reporter if testCase.Name != "" { - logging.Infof(" [%2d] '%s'", testCase.index, testCase.Name) + log.Infof(" [%2d] '%s'", testCase.index, testCase.Name) } else { - logging.Infof(" [%2d] ''", testCase.index) + log.Infof(" [%2d] ''", testCase.index) } r.NewChild(testCase.Name) @@ -62,14 +61,14 @@ func (testCase Case) runAPITestCase() (success bool) { if testCase.dataStore == nil && len(testCase.Store) > 0 { err := fmt.Errorf("error setting datastore. Datastore is nil") r.SaveToReportLog(fmt.Sprintf("Error during execution: %s", err)) - logging.Errorf(" [%2d] %s", testCase.index, err) + log.Errorf(" [%2d] %s", testCase.index, err) return false } err := testCase.dataStore.SetMap(testCase.Store) if err != nil { err = fmt.Errorf("error setting datastore map:%s", err) r.SaveToReportLog(fmt.Sprintf("Error during execution: %s", err)) - logging.Errorf(" [%2d] %s", testCase.index, err) + log.Errorf(" [%2d] %s", testCase.index, err) return false } @@ -82,18 +81,14 @@ func (testCase Case) runAPITestCase() (success bool) { if err != nil { r.SaveToReportLog(fmt.Sprintf("Error during execution: %s", err)) - logging.Errorf(" [%2d] %s", testCase.index, err) + log.Errorf(" [%2d] %s", testCase.index, err) success = false } if !success { - logging.WarnWithFieldsF( - map[string]interface{}{"elapsed": elapsed.Seconds()}, - " [%2d] %s", testCase.index, "failure") + log.WithFields(log.Fields{"elapsed": elapsed.Seconds()}).Warnf(" [%2d] failure", testCase.index) } else { - logging.InfoWithFieldsF( - map[string]interface{}{"elapsed": elapsed.Seconds()}, - " [%2d] %s", testCase.index, "success") + log.WithFields(log.Fields{"elapsed": elapsed.Seconds()}).Infof(" [%2d] success", testCase.index) } r.LeaveChild(success) @@ -178,7 +173,7 @@ func (testCase *Case) checkCollectResponse(request api.Request, response api.Res testCase.CollectResponse = leftResponses - logging.Debugf("Remaining CheckReponses: %s", testCase.CollectResponse) + log.Trace("Remaining CheckReponses: %s", testCase.CollectResponse) return len(leftResponses), nil } @@ -203,9 +198,10 @@ func (testCase Case) executeRequest(counter int) ( err = fmt.Errorf("error loading request: %s", err) return } - apitestLogging.DebugWithVerbosityf( - apitestLogging.V1, - "request: %s", req.ToString()) + + //Log request on trace level (so only v2 will trigger this) + log.Tracef("[REQUEST]:\n%s", req.ToString()) + apiResp, err = req.Send() if err != nil { err = fmt.Errorf("error sending request: %s", err) @@ -241,9 +237,7 @@ func (testCase Case) executeRequest(counter int) ( err = fmt.Errorf("error loading response: %s", err) return } - apitestLogging.DebugWithVerbosityf( - apitestLogging.V1, - "response: %s", apiResp.ToString()) + responsesMatch, err = testCase.responsesEqual(response, apiResp) if err != nil { err = fmt.Errorf("error matching responses: %s", err) @@ -253,13 +247,10 @@ func (testCase Case) executeRequest(counter int) ( return } -func LogReqResp(request api.Request, response api.Response) { - apitestLogging.DebugWithVerbosityf( - apitestLogging.V0, - "[Request] %s", request.ToString()) - apitestLogging.DebugWithVerbosityf( - apitestLogging.V0, - "[Response] %s", response.ToString()) +func LogResp(response api.Response) { + if FylrConfig.Apitest.LogVerbosity == 0 { + log.Debugf("[RESPONSE]:\n%s", response.ToString()) + } } func (testCase Case) run() (success bool, err error) { @@ -280,7 +271,12 @@ func (testCase Case) run() (success bool, err error) { //Poll repeats the request until the right response is found, or a timeout triggers for { responsesMatch, request, apiResponse, err = testCase.executeRequest(requestCounter) + if FylrConfig.Apitest.LogVerbosity >= 1 { + log.Debugf("[RESPONSE]:\n%s", apiResponse.ToString()) + } + if err != nil { + LogResp(apiResponse) return false, err } @@ -290,18 +286,18 @@ func (testCase Case) run() (success bool, err error) { breakPresent, err := testCase.breakResponseIsPresent(request, apiResponse) if err != nil { - LogReqResp(request, apiResponse) + LogResp(apiResponse) return false, fmt.Errorf("error checking for break response: %s", err) } if breakPresent { - LogReqResp(request, apiResponse) + LogResp(apiResponse) return false, fmt.Errorf("Break response found") } collectLeft, err := testCase.checkCollectResponse(request, apiResponse) if err != nil { - LogReqResp(request, apiResponse) + LogResp(apiResponse) return false, fmt.Errorf("error checking for continue response: %s", err) } @@ -313,7 +309,7 @@ func (testCase Case) run() (success bool, err error) { //break if timeout or we do not have a repeater if timedOut := time.Now().Sub(startTime) > (time.Duration(testCase.Timeout) * time.Millisecond); timedOut && testCase.Timeout != -1 { if timedOut && testCase.Timeout > 0 { - logging.Warnf("Pull Timeout '%dms' exceeded", testCase.Timeout) + log.Warnf("Pull Timeout '%dms' exceeded", testCase.Timeout) r.SaveToReportLogF("Pull Timeout '%dms' exceeded", testCase.Timeout) timedOutFlag = true } @@ -330,27 +326,25 @@ func (testCase Case) run() (success bool, err error) { } if !responsesMatch.Equal || timedOutFlag { - //logging.Warnf("responses did not match: Expected: %s \n Actual: %s",response.ToString(), apiResponse.ToString()) for _, v := range responsesMatch.Failures { - logging.Warnf("[%s] %s", v.Key, v.Message) + log.Infof("[%s] %s", v.Key, v.Message) r.SaveToReportLog(fmt.Sprintf("[%s] %s", v.Key, v.Message)) } - //logging.Warnf("responses did not match: Expected: %s \n Actual: %s",response.ToString(), apiResponse.ToString()) collectArray, ok := testCase.CollectResponse.(util.JsonArray) if ok { for _, v := range collectArray { jsonV, err := json.Marshal(v) if err != nil { - LogReqResp(request, apiResponse) + LogResp(apiResponse) return false, err } - logging.Warnf("Collect response not found: %s", jsonV) + log.Warnf("Collect response not found: %s", jsonV) r.SaveToReportLog(fmt.Sprintf("Collect response not found: %s", jsonV)) } } - LogReqResp(request, apiResponse) + LogResp(apiResponse) return false, nil } diff --git a/api_testsuite.go b/api_testsuite.go index c5e3e45..3f6fbad 100644 --- a/api_testsuite.go +++ b/api_testsuite.go @@ -12,9 +12,9 @@ import ( "github.com/programmfabrik/fylr-apitest/lib/api" "github.com/programmfabrik/fylr-apitest/lib/cjson" "github.com/programmfabrik/fylr-apitest/lib/filesystem" - "github.com/programmfabrik/fylr-apitest/lib/logging" "github.com/programmfabrik/fylr-apitest/lib/report" "github.com/programmfabrik/fylr-apitest/lib/template" + log "github.com/sirupsen/logrus" ) // Suite defines the structure of our apitest @@ -76,7 +76,7 @@ func NewTestSuite( func (ats Suite) Run() (success bool) { r := ats.reporter - logging.Infof("[%2d] '%s'", ats.index, ats.Name) + log.Infof("[%2d] '%s'", ats.index, ats.Name) r.NewChild(ats.Name) r.SetTestCount(len(ats.Tests)) @@ -87,9 +87,9 @@ func (ats Suite) Run() (success bool) { r.LeaveChild(success) if success { - logging.InfoWithFieldsF(map[string]interface{}{"elapsed": elapsed.Seconds()}, "[%2d] success", ats.index) + log.WithFields(log.Fields{"elapsed": elapsed.Seconds()}).Infof("[%2d] success", ats.index) } else { - logging.WarnWithFieldsF(map[string]interface{}{"elapsed": elapsed.Seconds()}, "[%2d] failure", ats.index) + log.WithFields(log.Fields{"elapsed": elapsed.Seconds()}).Warnf("[%2d] failure", ats.index) } return } @@ -113,7 +113,7 @@ func (ats Suite) runRequirements() (success bool) { return true } - logging.Infof("[%2d] %s", ats.index, "run requirements") + log.Infof("[%2d] %s", ats.index, "run requirements") for _, parentPath := range ats.RequirePaths { suite, err := NewTestSuite( ats.Config, @@ -125,18 +125,18 @@ func (ats Suite) runRequirements() (success bool) { ) if err != nil { r.SaveToReportLog(err.Error()) - logging.Errorf("[%2d] error loading parent suite: %s", ats.index, err) + log.Warnf("[%2d] error loading parent suite: %s", ats.index, err) return false } pSuccess := suite.Run() if !pSuccess { - logging.Warnf("[%2d] requirements: failure", ats.index) + log.Warnf("[%2d] requirements: failure", ats.index) return false } } - logging.Infof("[%2d] requirements: success", ats.index) + log.Infof("[%2d] requirements: success", ats.index) return true } @@ -164,14 +164,14 @@ func (ats Suite) runTestCases() (success bool) { intFilepath, testObj, err := template.LoadManifestDataAsObject(v, ats.manifestDir, loader) if err != nil { r.SaveToReportLog(err.Error()) - logging.Error(err) + log.Error(err) return false } testJson, err := json.Marshal(testObj) if err != nil { r.SaveToReportLog(err.Error()) - logging.Error(err) + log.Error(err) return false } @@ -181,7 +181,7 @@ func (ats Suite) runTestCases() (success bool) { err = json.Unmarshal(testJson, &singleTest) if err != nil { r.SaveToReportLog(err.Error()) - logging.Error(err) + log.Error(err) return false } diff --git a/config.go b/config.go index 420c3ce..b2aed5f 100644 --- a/config.go +++ b/config.go @@ -27,6 +27,7 @@ type FylrConfigStruct struct { File string `mapstructure:"file"` Format string `mapstructure:"format"` } `mapstructure:"report"` + LogVerbosity int } } diff --git a/lib/api/build_policies.go b/lib/api/build_policies.go index d6eebc4..9fd8b35 100644 --- a/lib/api/build_policies.go +++ b/lib/api/build_policies.go @@ -13,7 +13,7 @@ import ( ) func buildMultipart(request Request) (additionalHeaders map[string]string, body io.Reader, err error) { - additionalHeaders = make(map[string]string) + additionalHeaders = make(map[string]string, 0) var buf = bytes.NewBuffer([]byte{}) w := multipart.NewWriter(buf) @@ -40,14 +40,14 @@ func buildMultipart(request Request) (additionalHeaders map[string]string, body return additionalHeaders, nil, err } } - w.Close() - + err = w.Close() body = bytes.NewBuffer(buf.Bytes()) - return additionalHeaders, body, nil + + return } func buildUrlencoded(request Request) (additionalHeaders map[string]string, body io.Reader, err error) { - additionalHeaders = make(map[string]string) + additionalHeaders = make(map[string]string, 0) additionalHeaders["Content-Type"] = "application/x-www-form-urlencoded" formParams := url.Values{} for key, value := range request.Body.(map[string]string) { @@ -59,7 +59,7 @@ func buildUrlencoded(request Request) (additionalHeaders map[string]string, body } func buildRegular(request Request) (additionalHeaders map[string]string, body io.Reader, err error) { - additionalHeaders = make(map[string]string) + additionalHeaders = make(map[string]string, 0) additionalHeaders["Content-Type"] = "application/json" bodyBytes, err := cjson.Marshal(request.Body) if err != nil { diff --git a/lib/api/datastore.go b/lib/api/datastore.go index 634e425..4600a20 100644 --- a/lib/api/datastore.go +++ b/lib/api/datastore.go @@ -2,7 +2,7 @@ package api import ( "fmt" - "github.com/programmfabrik/fylr-apitest/lib/logging" + log "github.com/sirupsen/logrus" "regexp" "strconv" "strings" @@ -132,7 +132,7 @@ func (this *Datastore) Set(index string, value interface{}) error { this.storage[index] = value } - logging.Debugf("Did set datastore[\"%s\"]=%#v", index, value) + log.Debugf("Did set datastore[\"%s\"]=%#v", index, value) return nil } diff --git a/lib/api/request.go b/lib/api/request.go index 5eeb943..5f218ae 100644 --- a/lib/api/request.go +++ b/lib/api/request.go @@ -2,7 +2,6 @@ package api import ( "fmt" - "github.com/programmfabrik/fylr-apitest/lib/logging" "github.com/programmfabrik/fylr-apitest/lib/util" "time" @@ -121,7 +120,7 @@ func (request Request) ToString() (res string) { } else { dumpBody = true } - resBytes, err := httputil.DumpRequest(httpRequest, dumpBody) + resBytes, err := httputil.DumpRequestOut(httpRequest, dumpBody) if err != nil { return fmt.Sprintf("could not dump httpRequest: %s", err) } @@ -129,7 +128,6 @@ func (request Request) ToString() (res string) { } func (request Request) Send() (response Response, err error) { - logging.DebugWithVerbosityf(logging.V2, "request: %s", request.ToString()) httpRequest, err := request.buildHttpRequest() if err != nil { return response, err @@ -144,7 +142,5 @@ func (request Request) Send() (response Response, err error) { if err != nil { return response, fmt.Errorf("error constructing response from http response") } - logging.DebugWithVerbosityf(logging.V2, "response: %s", response.ToString()) - return response, err } diff --git a/lib/api/response.go b/lib/api/response.go index 711022c..dbf6bee 100644 --- a/lib/api/response.go +++ b/lib/api/response.go @@ -9,7 +9,6 @@ import ( "io/ioutil" ) - type Response struct { statusCode int headers map[string][]string @@ -67,7 +66,7 @@ func (response Response) ToJsonString() (string, error) { if err != nil { return "", fmt.Errorf("error formatting respsone: %s", err) } - json, err := cjson.Marshal(gj) + json, err := cjson.MarshalIndent(gj, "", " ") if err != nil { return "", fmt.Errorf("error formatting response: %s", err) } @@ -93,5 +92,14 @@ func (response *Response) marshalBodyInto(target interface{}) (err error) { } func (response Response) ToString() (res string) { - return fmt.Sprintf("Statuscode: %d, Body: %s", response.statusCode, string(response.Body())) + headersString := "" + for k, v := range response.headers { + value := "" + for _, iv := range v { + value = fmt.Sprintf("%s %s", value, iv) + + } + headersString = fmt.Sprintf("%s\n%s:%s", headersString, k, value) + } + return fmt.Sprintf("%d\n%s\n\n%s", response.statusCode, headersString, string(response.Body())) } diff --git a/lib/logging/multilogger.go b/lib/logging/multilogger.go deleted file mode 100644 index fd169a2..0000000 --- a/lib/logging/multilogger.go +++ /dev/null @@ -1,236 +0,0 @@ -package logging - -import ( - "bytes" - "fmt" - "io/ioutil" - "log" - "os" - "github.com/sirupsen/logrus" -) - -type LogInfo map[string]interface{} - -/* -Application wide logging package -*/ - -func init() { - ConfigureLogging(false, "info") -} - -type multiLogger struct { - loggers []*logrus.Logger -} - -func newMultiLogger() *multiLogger { - return &multiLogger{} -} - -func (multi *multiLogger) addLogger(logger *logrus.Logger) { - multi.loggers = append(multi.loggers, logger) -} - -var multi *multiLogger - - - -type enumVerbosity int - -const ( - V_ enumVerbosity = -1 - V0 enumVerbosity = 0 - V1 enumVerbosity = 1 - V2 enumVerbosity = 2 -) - -var _verbosity enumVerbosity - -func InitApiTestLogging(v int) error { - switch v { - case 0: - _verbosity = V0 - case 1: - _verbosity = V1 - case 2: - _verbosity = V2 - case -1: - _verbosity = V_ - default: - return fmt.Errorf("verbosity must be -1, 0, 1 or 2") - } - if v >= 0 { - if err := SetLevel("debug"); err != nil { - return err - } - } - return nil -} - -func DebugWithVerbosity(verbosity enumVerbosity, msg string) { - if verbosity == _verbosity { - Debug(msg) - } -} - -func DebugWithVerbosityf(verbosity enumVerbosity, format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - DebugWithVerbosity(verbosity, msg) -} - - -//Initializes Logging with global config -func ConfigureLogging( - consoleEnabled bool, - consoleLevel string) (err error) { - - multi = newMultiLogger() - - if consoleEnabled { - consoleLogger, err := newConsoleLogger(consoleLevel) - if err != nil { - return fmt.Errorf("error adding consoleLogger: %s", err) - } - multi.addLogger(consoleLogger) - log.Printf("Console: { enabled: %t; level: %s }\n", consoleEnabled, consoleLevel) - } - - return nil -} - -//Set Loglevel for active loggers -func SetLevel(lvl string) error { - logLevel, err := logrus.ParseLevel(lvl) - if err != nil { - return err - } - for _, logger := range multi.loggers { - logger.SetLevel(logLevel) - } - return nil -} - -//Set Output for active loggers; needed to capture logging in Unittests -func SetOutput(buf *bytes.Buffer) { - for _, logger := range multi.loggers { - logger.SetOutput(buf) - } -} - -func newConsoleLogger(lvl string) (*logrus.Logger, error) { - logLevel, err := logrus.ParseLevel(lvl) - if err != nil { - return nil, fmt.Errorf("error parsing loglevel: %s", err) - } - logger := logrus.New() - logger.Out = os.Stderr - logger.SetLevel(logLevel) - return logger, nil -} - -//Method to allow subpackages to intercept logging statements -func AddHook(hook logrus.Hook) { - logger := logrus.New() - logger.Out = ioutil.Discard - logger.AddHook(hook) - multi.addLogger(logger) -} - -//LOGRUS Package Methods -// includes Debug, Info, Warn, Error and the *f variants; NOT the *ln variants - -// Debug logs a message at level Debug on the standard logger. -func Debug(args ...interface{}) { - for _, logger := range multi.loggers { - logger.Debug(args...) - } -} - -// Info logs a message at level Info on the standard logger. -func Info(args ...interface{}) { - for _, logger := range multi.loggers { - logger.Info(args...) - } -} - -// Warn logs a message at level Warn on the standard logger. -func Warn(args ...interface{}) { - for _, logger := range multi.loggers { - logger.Warn(args...) - } -} - -// Error logs a message at level Error on the standard logger. -func Error(args ...interface{}) { - for _, logger := range multi.loggers { - logger.Error(args...) - } -} - -// Debugf logs a message at level Debug on the standard logger. -func Debugf(format string, args ...interface{}) { - for _, logger := range multi.loggers { - logger.Debugf(format, args...) - } -} - -// Infof logs a message at level Info on the standard logger. -func Infof(format string, args ...interface{}) { - for _, logger := range multi.loggers { - logger.Infof(format, args...) - } -} - -// Warnf logs a message at level Warn on the standard logger. -func Warnf(format string, args ...interface{}) { - for _, logger := range multi.loggers { - logger.Warnf(format, args...) - } -} - -// Errorf logs a message at level Error on the standard logger. -func Errorf(format string, args ...interface{}) { - for _, logger := range multi.loggers { - logger.Errorf(format, args...) - } -} - -func InfoWithFieldsF(fields map[string]interface{}, msg string, args ...interface{}) { - for _, logger := range multi.loggers { - entry := logrus.NewEntry(logger) - for key, val := range fields { - entry = entry.WithFields(logrus.Fields{key: val}) - } - entry.Infof(msg, args...) - } -} - -func DebugWithFieldsF(fields map[string]interface{}, msg string, args ...interface{}) { - for _, logger := range multi.loggers { - entry := logrus.NewEntry(logger) - for key, val := range fields { - entry = entry.WithFields(logrus.Fields{key: val}) - } - entry.Debugf(msg, args...) - } -} - -func ErrorWithFieldsF(fields map[string]interface{}, msg string, args ...interface{}) { - for _, logger := range multi.loggers { - entry := logrus.NewEntry(logger) - for key, val := range fields { - entry = entry.WithFields(logrus.Fields{key: val}) - } - entry.Errorf(msg, args...) - } -} - -func WarnWithFieldsF(fields map[string]interface{}, msg string, args ...interface{}) { - for _, logger := range multi.loggers { - entry := logrus.NewEntry(logger) - for key, val := range fields { - entry = entry.WithFields(logrus.Fields{key: val}) - } - entry.Warnf(msg, args...) - } -} diff --git a/lib/template/template_loader.go b/lib/template/template_loader.go index 5b3825c..8df37b9 100644 --- a/lib/template/template_loader.go +++ b/lib/template/template_loader.go @@ -3,13 +3,13 @@ package template import ( "bytes" "fmt" + log "github.com/sirupsen/logrus" "regexp" "strings" "text/template" "github.com/programmfabrik/fylr-apitest/lib/cjson" "github.com/programmfabrik/fylr-apitest/lib/csv" - "github.com/programmfabrik/fylr-apitest/lib/logging" "github.com/programmfabrik/fylr-apitest/lib/util" "io/ioutil" @@ -88,7 +88,7 @@ func (loader *Loader) Render( err = fmt.Errorf("The given json was empty") return } - logging.Debug(fmt.Sprintf("[QJSON] JSON input: %s", json)) + log.Tracef("[QJSON] JSON input: %s", json) result = gjson.Get(json, path).Raw if len(result) == 0 { diff --git a/main.go b/main.go index 88b2b53..2f29e9a 100644 --- a/main.go +++ b/main.go @@ -6,10 +6,11 @@ import ( "fmt" "github.com/programmfabrik/fylr-apitest/lib/api" - "github.com/programmfabrik/fylr-apitest/lib/logging" + log "github.com/sirupsen/logrus" + "github.com/programmfabrik/fylr-apitest/lib/report" "io/ioutil" - "log" + "path/filepath" "github.com/spf13/cobra" @@ -27,8 +28,7 @@ var ( func init() { //Configure all the flags that fylr-apitest offers TestCMD.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./fylr.yml", "config file") - TestCMD.PersistentFlags().String("log-console-level", "info", "console loglevel") - TestCMD.PersistentFlags().Bool("log-console-enable", true, "Set to true to enable console logging") + TestCMD.PersistentFlags().StringSliceVarP( &rootDirectorys, "directory", "d", []string{"."}, "path to directory containing the tests.") @@ -44,11 +44,10 @@ func init() { TestCMD.PersistentFlags().IntVarP( &verbosity, "verbosity", "v", -1, `in [-1, 0, 1, 2], defines logging of requests and responses of the programm --1: dont log requests or responses -0: log requests and responses in case of test failure -1: log requests and responses that are documented in the manifest -2: log all requests and responses -verbosities >= 0 automatically set the global loglevel to debug`) +-1: Only normal test ouput +0: All from '-1' & failed test responses +1: All from '-1' & all responses +2: All from '1' & all requests`) TestCMD.PersistentFlags().StringVar( &reportFile, "report-file", "", @@ -59,8 +58,6 @@ verbosities >= 0 automatically set the global loglevel to debug`) "Defines how the report statements should be saved. [junit/json]") //Bind the flags to overwrite the yml config if they are set - viper.BindPFlag("log.console.enable", TestCMD.PersistentFlags().Lookup("log-console-enable")) - viper.BindPFlag("log.console.level", TestCMD.PersistentFlags().Lookup("log-console-level")) viper.BindPFlag("apitest.report.file", TestCMD.PersistentFlags().Lookup("report-file")) viper.BindPFlag("apitest.report.format", TestCMD.PersistentFlags().Lookup("report-format")) } @@ -88,11 +85,14 @@ func setup(ccmd *cobra.Command, args []string) { //Load yml config LoadConfig(cfgFile) - //Setup logging - if err := logging.ConfigureLogging( - viper.GetBool("log.console.enable"), - viper.GetString("log.console.level")); err != nil { - log.Fatalf("error configuring logging: %s", err) + //Set log verbosity + FylrConfig.Apitest.LogVerbosity = verbosity + if verbosity >= 2 { + log.SetLevel(log.TraceLevel) + } else if verbosity >= 0 { + log.SetLevel(log.DebugLevel) + } else { + log.SetLevel(log.InfoLevel) } } @@ -104,22 +104,15 @@ func runApiTests(cmd *cobra.Command, args []string) { for _, rootDirectory := range rootDirectorys { if _, err := os.Stat(rootDirectory); rootDirectory != "." && os.IsNotExist(err) { - logging.Errorf("The path '%s' for the test folders is not valid", rootDirectory) - os.Exit(1) + log.Fatalf("The path '%s' for the test folders is not valid", rootDirectory) } } for _, singleTest := range singleTests { if _, err := os.Stat(singleTest); singleTest != "" && os.IsNotExist(err) { - logging.Errorf("The path '%s' for the single test is not valid", singleTest) - os.Exit(1) + log.Fatalf("The path '%s' for the single test is not valid", singleTest) } } - if err := logging.InitApiTestLogging(verbosity); err != nil { - logging.Errorf("Could not configure verbosity of apitest logging") - os.Exit(1) - } - serverUrl := FylrConfig.Apitest.Server dbName := FylrConfig.Apitest.DBName reportFormat = FylrConfig.Apitest.Report.Format @@ -128,14 +121,13 @@ func runApiTests(cmd *cobra.Command, args []string) { //Save the config into TestToolConfig testToolConfig, err := NewTestToolConfig(serverUrl, dbName, rootDirectorys) if err != nil { - logging.Error(err) - os.Exit(1) + log.Fatal(err) } datastore := api.NewStore() for k, v := range FylrConfig.Apitest.StoreInit { datastore.Set(k, v) - logging.Infof("Add Init value for datastore Key: '%s', Value: '%v'", k, v) + log.Debugf("Add Init value for datastore Key: '%s', Value: '%v'", k, v) } //Actually run the tests @@ -150,8 +142,7 @@ func runApiTests(cmd *cobra.Command, args []string) { 0, ) if err != nil { - logging.Error(err) - os.Exit(1) + log.Fatal(err) } suite.Run() @@ -182,7 +173,7 @@ func runApiTests(cmd *cobra.Command, args []string) { case "json": parsingFunction = report.ParseJSONResult default: - logging.Errorf( + log.Errorf( "Given report format '%s' not supported. Saving report '%s' as json", reportFormat, reportFile) From d648abbe6a12dbabc93163010b68696bdef01e0d Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 6 Feb 2019 14:16:36 +0100 Subject: [PATCH 08/17] Allow per testcase setting of the loglevel --- README.md | 4 ++++ api_testcase.go | 6 ++++++ config.go | 13 ++++++++++++- main.go | 9 +-------- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 82dd6dc..fdce9b1 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ This starts the command with the following default settings: - `-v 0`: All from '-1' plus failed test responses - `-v 1`: All from '-1' plus all responses - `-v 2`: All from '1' plus all requests + +You can also set the log verbosity per single testcase. The greater verbosity wins. #### Console logging @@ -142,6 +144,8 @@ Manifest is loaded as **template**, so you can use variables, Go **range** and * "key1": "value1", "key2": "value2" }, + // Specify a unique log level only for this single test. (If cli has a greate log verbosity than this, cli wins) + "log_verbosity": 2, //Defines what gets send to the server "request": { //What endpoint we want to target. You find all possible endpoints in the api documentation diff --git a/api_testcase.go b/api_testcase.go index c0dddec..c7529b2 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -34,6 +34,7 @@ type Case struct { Delay *int `json:"delay_ms"` BreakResponse []util.GenericJson `json:"break_response"` CollectResponse util.GenericJson `json:"collect_response"` + LogVerbosity *int `json:"log_verbosity"` loader template.Loader manifestDir string @@ -45,6 +46,11 @@ type Case struct { } func (testCase Case) runAPITestCase() (success bool) { + if testCase.LogVerbosity != nil && *testCase.LogVerbosity > FylrConfig.Apitest.LogVerbosity { + defer FylrConfig.SetLogVerbosity(FylrConfig.Apitest.LogVerbosity) + FylrConfig.SetLogVerbosity(*testCase.LogVerbosity) + } + r := testCase.reporter if testCase.Name != "" { diff --git a/config.go b/config.go index b2aed5f..3017328 100644 --- a/config.go +++ b/config.go @@ -3,9 +3,9 @@ package main import ( "fmt" "github.com/programmfabrik/fylr-apitest/lib/filesystem" + log "github.com/sirupsen/logrus" "github.com/spf13/afero" "github.com/spf13/viper" - "log" "os" "path/filepath" "time" @@ -52,6 +52,17 @@ func LoadConfig(cfgFile string) { } +func (config *FylrConfigStruct) SetLogVerbosity(verbosity int) { + FylrConfig.Apitest.LogVerbosity = verbosity + if verbosity >= 2 { + log.SetLevel(log.TraceLevel) + } else if verbosity >= 0 { + log.SetLevel(log.DebugLevel) + } else { + log.SetLevel(log.InfoLevel) + } +} + // TestToolConfig gives us the basic testtool infos type TestToolConfig struct { ServerURL string diff --git a/main.go b/main.go index 2f29e9a..15969b2 100644 --- a/main.go +++ b/main.go @@ -86,14 +86,7 @@ func setup(ccmd *cobra.Command, args []string) { LoadConfig(cfgFile) //Set log verbosity - FylrConfig.Apitest.LogVerbosity = verbosity - if verbosity >= 2 { - log.SetLevel(log.TraceLevel) - } else if verbosity >= 0 { - log.SetLevel(log.DebugLevel) - } else { - log.SetLevel(log.InfoLevel) - } + FylrConfig.SetLogVerbosity(verbosity) } func runApiTests(cmd *cobra.Command, args []string) { From df98087e6e040c56a5a15343426b216b6885a6a2 Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 6 Feb 2019 14:29:44 +0100 Subject: [PATCH 09/17] Go vet & Go test fixups --- api_testcase.go | 2 +- lib/api/response.go | 2 +- lib/api/response_test.go | 2 +- lib/csv/csv.go | 3 --- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/api_testcase.go b/api_testcase.go index c7529b2..26efe4d 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -179,7 +179,7 @@ func (testCase *Case) checkCollectResponse(request api.Request, response api.Res testCase.CollectResponse = leftResponses - log.Trace("Remaining CheckReponses: %s", testCase.CollectResponse) + log.Tracef("Remaining CheckReponses: %s", testCase.CollectResponse) return len(leftResponses), nil } diff --git a/lib/api/response.go b/lib/api/response.go index dbf6bee..9258608 100644 --- a/lib/api/response.go +++ b/lib/api/response.go @@ -66,7 +66,7 @@ func (response Response) ToJsonString() (string, error) { if err != nil { return "", fmt.Errorf("error formatting respsone: %s", err) } - json, err := cjson.MarshalIndent(gj, "", " ") + json, err := cjson.Marshal(gj) if err != nil { return "", fmt.Errorf("error formatting response: %s", err) } diff --git a/lib/api/response_test.go b/lib/api/response_test.go index 014f3bb..e1223d3 100644 --- a/lib/api/response_test.go +++ b/lib/api/response_test.go @@ -72,7 +72,7 @@ func TestResponse_NewResponse(t *testing.T) { func TestResponse_String(t *testing.T) { response, err := NewResponse(200, nil, strings.NewReader("{\"foo\": \"bar\"}")) - assertString := "Statuscode: 200, Body: {\"foo\": \"bar\"}" + assertString := "200\n\n\n{\"foo\": \"bar\"}" test_utils.CheckError(t, err, "error constructing response") test_utils.AssertStringEquals(t, response.ToString(), assertString) } diff --git a/lib/csv/csv.go b/lib/csv/csv.go index 16bf9d8..d87404f 100644 --- a/lib/csv/csv.go +++ b/lib/csv/csv.go @@ -249,7 +249,4 @@ func getTyped(value, format string) (interface{}, error) { default: return nil, fmt.Errorf("Given format '%s' not supported for csv usage", format) } - - return nil, fmt.Errorf("Given format '%s' not supported for csv usage", format) - } From c470ea4dc3e5ca13a60f9e60d4cd3ef3dff7414b Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 6 Feb 2019 16:25:19 +0100 Subject: [PATCH 10/17] Global Headers and Headers from Datastore per testsuite. They get overwriten by testcase level --- api_testcase.go | 25 ++++++++++++++++++++++++- api_testsuite.go | 5 +++++ lib/api/datastore.go | 2 +- lib/api/request.go | 12 +++++++++--- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/api_testcase.go b/api_testcase.go index 26efe4d..73914b7 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -42,7 +42,11 @@ type Case struct { suiteIndex int index int dataStore *api.Datastore - ServerURL string + + standardHeader map[string]*string + standardHeaderFromStore map[string]string + + ServerURL string } func (testCase Case) runAPITestCase() (success bool) { @@ -407,6 +411,25 @@ func (testCase Case) loadRequestSerialization() (spec api.Request, err error) { spec.ManifestDir = testCase.manifestDir spec.DataStore = testCase.dataStore spec.ServerURL = testCase.ServerURL + + if len(spec.Headers) == 0 { + spec.Headers = make(map[string]*string, 0) + } + for k, v := range testCase.standardHeader { + if spec.Headers[k] == nil { + spec.Headers[k] = v + } + } + + if len(spec.HeaderFromStore) == 0 { + spec.HeaderFromStore = make(map[string]string, 0) + } + for k, v := range testCase.standardHeaderFromStore { + if spec.HeaderFromStore[k] == "" { + spec.HeaderFromStore[k] = v + } + } + return } diff --git a/api_testsuite.go b/api_testsuite.go index 3f6fbad..6c270cb 100644 --- a/api_testsuite.go +++ b/api_testsuite.go @@ -26,6 +26,9 @@ type Suite struct { RequirePaths []string `json:"require"` Store map[string]interface{} `json:"store"` + StandardHeader map[string]*string `yaml:"header" json:"header"` + StandardHeaderFromStore map[string]string `yaml:"header_from_store" json:"header_from_store"` + Config TestToolConfig datastore *api.Datastore manifestDir string @@ -196,6 +199,8 @@ func (ats Suite) runTestCases() (success bool) { test.index = k test.dataStore = ats.datastore test.ServerURL = ats.Config.ServerURL + test.standardHeader = ats.StandardHeader + test.standardHeaderFromStore = ats.StandardHeaderFromStore success := test.runAPITestCase() diff --git a/lib/api/datastore.go b/lib/api/datastore.go index 4600a20..fd3938f 100644 --- a/lib/api/datastore.go +++ b/lib/api/datastore.go @@ -132,7 +132,7 @@ func (this *Datastore) Set(index string, value interface{}) error { this.storage[index] = value } - log.Debugf("Did set datastore[\"%s\"]=%#v", index, value) + log.Tracef("Set datastore[\"%s\"]=%#v", index, value) return nil } diff --git a/lib/api/request.go b/lib/api/request.go index 5f218ae..7a5a743 100644 --- a/lib/api/request.go +++ b/lib/api/request.go @@ -14,7 +14,7 @@ var c http.Client func init() { c = http.Client{ - Timeout: time.Second * 30, + Timeout: time.Minute * 5, } } @@ -23,7 +23,7 @@ type Request struct { ServerURL string `yaml:"serverurl" json:"serverurl"` Method string `yaml:"method" json:"method"` QueryParams map[string]interface{} `yaml:"query_params" json:"query_params"` - Headers map[string]string `yaml:"header" json:"header"` + Headers map[string]*string `yaml:"header" json:"header"` HeaderFromStore map[string]string `yaml:"header_from_store" json:"header_from_store"` BodyType string `yaml:"body_type" json:"body_type"` Body util.GenericJson `yaml:"body" json:"body"` @@ -98,7 +98,13 @@ func (request Request) buildHttpRequest() (res *http.Request, err error) { } for key, val := range request.Headers { - res.Header.Add(key, val) + if *val == "" { + //Unset header explicit + res.Header.Del(key) + } else { + //ADD header + res.Header.Add(key, *val) + } } for key, val := range additionalHeaders { From 030254e20023fcc2d02679c9ee528957889f1f93 Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Mon, 11 Feb 2019 12:03:57 +0100 Subject: [PATCH 11/17] api_testsuite.go: Remove require manifests and allow infinite inline @ chains --- api_testsuite.go | 220 ++++++++++++++++++++++++++----------------- lib/template/util.go | 27 ++++++ 2 files changed, 163 insertions(+), 84 deletions(-) diff --git a/api_testsuite.go b/api_testsuite.go index 6c270cb..3d41853 100644 --- a/api_testsuite.go +++ b/api_testsuite.go @@ -20,11 +20,10 @@ import ( // Suite defines the structure of our apitest // We do read this in with the config loader type Suite struct { - Name string `json:"name"` - Description string `json:"description"` - Tests []util.GenericJson `json:"tests"` - RequirePaths []string `json:"require"` - Store map[string]interface{} `json:"store"` + Name string `json:"name"` + Description string `json:"description"` + Tests []util.GenericJson `json:"tests"` + Store map[string]interface{} `json:"store"` StandardHeader map[string]*string `yaml:"header" json:"header"` StandardHeaderFromStore map[string]string `yaml:"header_from_store" json:"header_from_store"` @@ -98,102 +97,30 @@ func (ats Suite) Run() (success bool) { } func (ats Suite) run() (success bool) { - if ats.executeRequirements { - success = ats.runRequirements() - if !success { - return false - } - } - - return ats.runTestCases() -} - -func (ats Suite) runRequirements() (success bool) { - //new empty reporter to don't include requirements in Testreport - r := report.NewReport() - - if ats.RequirePaths == nil { - return true - } - - log.Infof("[%2d] %s", ats.index, "run requirements") - for _, parentPath := range ats.RequirePaths { - suite, err := NewTestSuite( - ats.Config, - filepath.Join(ats.manifestDir, parentPath), - r, - ats.executeRequirements, - ats.datastore, - ats.index+1, - ) - if err != nil { - r.SaveToReportLog(err.Error()) - log.Warnf("[%2d] error loading parent suite: %s", ats.index, err) - return false - } - - pSuccess := suite.Run() - if !pSuccess { - log.Warnf("[%2d] requirements: failure", ats.index) - return false - } - } - - log.Infof("[%2d] requirements: success", ats.index) - return true -} - -/* -Runs TestCases of the TestSuite -We have to create the session at this point, otherwise -it might get deleted by a previous post to session/purgeall. -*/ -func (ats Suite) runTestCases() (success bool) { - /*defer func() { - if err := recover(); err != nil { - logging.Error(err) - success = false - } - }()*/ - r := ats.reporter - //datastoreShare := api.NewStoreShare(ats.datastore) loader := template.NewLoader(ats.datastore) for k, v := range ats.Tests { - tests := make([]Case, 0) - intFilepath, testObj, err := template.LoadManifestDataAsObject(v, ats.manifestDir, loader) + tests, err := ats.loadTestCaseByte(v, ats.manifestDir, loader) if err != nil { r.SaveToReportLog(err.Error()) - log.Error(err) + log.Error(fmt.Errorf("can not loadTestCaseByte %s", err)) return false } - testJson, err := json.Marshal(testObj) - if err != nil { - r.SaveToReportLog(err.Error()) - log.Error(err) - return false - } - - err = json.Unmarshal(testJson, &tests) - if err != nil { - singleTest := Case{} - err = json.Unmarshal(testJson, &singleTest) + for _, testContainer := range tests { + var test Case + err := json.Unmarshal(testContainer.CaseByte, &test) if err != nil { r.SaveToReportLog(err.Error()) - log.Error(err) + log.Error(fmt.Errorf("can not unmarshal single test %s", err)) return false } - tests = append(tests, singleTest) - } - - for _, test := range tests { test.loader = loader - test.manifestDir = filepath.Join(ats.manifestDir, intFilepath) + test.manifestDir = filepath.Join(ats.manifestDir, testContainer.Path) test.reporter = r test.suiteIndex = ats.index test.index = k @@ -207,12 +134,137 @@ func (ats Suite) runTestCases() (success bool) { if !success && !test.ContinueOnFailure { return false } + } } return true } +type TestUnmarsh struct { + CaseByte json.RawMessage + Path string +} + +func (ats Suite) loadTestCaseByte(v util.GenericJson, manifestDir string, loader template.Loader) ([]TestUnmarsh, error) { + rTests := make([]TestUnmarsh, 0) + + dir, testObj, err := template.LoadManifestDataAsRawJson(v, manifestDir) + if err != nil { + return rTests, err + } + + var testCases []json.RawMessage + err = cjson.Unmarshal(testObj, &testCases) + + if err != nil { + err = nil + + rTests = make([]TestUnmarsh, 0) + var singleTest json.RawMessage + err = cjson.Unmarshal(testObj, &singleTest) + if err == nil { + rTests = append(rTests, TestUnmarsh{CaseByte: singleTest, Path: filepath.Join(manifestDir, dir)}) + } else { + requestBytes, lErr := loader.Render(testObj, filepath.Join(manifestDir, dir), nil) + if lErr != nil { + return rTests, lErr + } + + if string(requestBytes) == string(testObj) { + return rTests, err + } + + tests, llErr := ats.loadTestCaseByte(requestBytes, filepath.Join(manifestDir, dir), loader) + if llErr != nil { + return rTests, llErr + } + rTests = append(rTests, tests...) + + err = nil + + } + } else { + for _, v := range testCases { + rTests = append(rTests, TestUnmarsh{CaseByte: v, Path: filepath.Join(manifestDir, dir)}) + } + } + + tempTests := make([]TestUnmarsh, 0) + + for _, v := range rTests { + + if rune(v.CaseByte[1]) == rune('@') { + var sS string + cjson.Unmarshal(v.CaseByte, &sS) + tests, err := ats.loadTestCaseByte(sS, v.Path, loader) + if err != nil { + return rTests, fmt.Errorf("could not load inner loadTestCaseByte: %s", err) + } + tempTests = append(tempTests, tests...) + } else { + requestBytes, err := loader.Render(v.CaseByte, v.Path, nil) + if err != nil { + return rTests, fmt.Errorf("could not render: %s", err) + } + v.CaseByte = requestBytes + + tempTests = append(tempTests, v) + } + } + + return tempTests, err +} + +/* + +func (ats Suite) unmarshalIntoTestCases(v util.GenericJson, loader template.Loader, manifestDir string)(rTests []TestUnmarsh, err error){ + rTests = make([]TestUnmarsh, 0) + + dir, testObj, err := template.LoadManifestDataAsRawJson(v, manifestDir) + if err != nil { + return rTests, err + } + + testJson, err := json.Marshal(testObj) + if err != nil { + return rTests, err + } + + var testCases []Case + err = json.Unmarshal(testJson, &testCases) + if err != nil { + rTests = make([]TestUnmarsh,0) + + var singleTest Case + err = json.Unmarshal(testJson, &singleTest) + if err != nil { + rTests = make([]TestUnmarsh,0) + err = nil + + var genJson []util.GenericJson + json.Unmarshal(testJson, &genJson) + for _,iv := range genJson{ + iTests, err := ats.unmarshalIntoTestCases(iv,loader,filepath.Join(ats.manifestDir, dir)) + if err != nil { + return rTests, err + } + rTests = append(rTests,iTests...) + } + + + }else { + rTests = append(rTests, TestUnmarsh{Case:singleTest,Path:dir}) + } + }else{ + for _,v := range testCases{ + rTests = append(rTests, TestUnmarsh{Case:v,Path:dir}) + } + } + + return +} +*/ func (ats Suite) loadManifest() (res []byte, err error) { loader := template.NewLoader(ats.datastore) manifestFile, err := filesystem.Fs.Open(ats.manifestPath) diff --git a/lib/template/util.go b/lib/template/util.go index 93d168c..47b793d 100644 --- a/lib/template/util.go +++ b/lib/template/util.go @@ -1,6 +1,7 @@ package template import ( + "encoding/json" "fmt" "io/ioutil" @@ -46,6 +47,7 @@ func LoadManifestDataAsObject(data util.GenericJson, manifestDir string, loader if err = cjson.Unmarshal(requestBytes, &jsonObject); err != nil { if err = cjson.Unmarshal(requestBytes, &jsonArray); err == nil { + return filepath, jsonArray, nil } return "", res, fmt.Errorf("error unmarshalling: %s", err) @@ -60,3 +62,28 @@ func LoadManifestDataAsObject(data util.GenericJson, manifestDir string, loader return "", res, fmt.Errorf("specification needs to be string[@...] or jsonObject but is: %s", data) } } + +func LoadManifestDataAsRawJson(data util.GenericJson, manifestDir string) (filepath string, res json.RawMessage, err error) { + switch typedData := data.(type) { + case []byte: + err = res.UnmarshalJSON(typedData) + return "", res, nil + case string: + filepath, res, err := loadFileFromPathSpec(typedData, manifestDir) + if err != nil { + return "", res, fmt.Errorf("error loading fileFromPathSpec: %s", err) + } + return filepath, res, nil + case util.JsonObject, util.JsonArray: + jsonMar, err := json.Marshal(typedData) + if err != nil { + return "", res, fmt.Errorf("error marshaling: %s", err) + } + if err = cjson.Unmarshal(jsonMar, &res); err != nil { + return "", res, fmt.Errorf("error unmarshalling: %s", err) + } + return "", res, nil + default: + return "", res, fmt.Errorf("specification needs to be string[@...] or jsonObject but is: %s", data) + } +} From ff3dfa23e15cc4d637f8b328e6ff9e8d000f4722 Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Mon, 11 Feb 2019 12:35:26 +0100 Subject: [PATCH 12/17] api_testsuite.go: Remove double manifest path --- api_testsuite.go | 51 +----------------------------------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/api_testsuite.go b/api_testsuite.go index 3d41853..5f96c2e 100644 --- a/api_testsuite.go +++ b/api_testsuite.go @@ -120,7 +120,7 @@ func (ats Suite) run() (success bool) { } test.loader = loader - test.manifestDir = filepath.Join(ats.manifestDir, testContainer.Path) + test.manifestDir = testContainer.Path test.reporter = r test.suiteIndex = ats.index test.index = k @@ -216,55 +216,6 @@ func (ats Suite) loadTestCaseByte(v util.GenericJson, manifestDir string, loader return tempTests, err } -/* - -func (ats Suite) unmarshalIntoTestCases(v util.GenericJson, loader template.Loader, manifestDir string)(rTests []TestUnmarsh, err error){ - rTests = make([]TestUnmarsh, 0) - - dir, testObj, err := template.LoadManifestDataAsRawJson(v, manifestDir) - if err != nil { - return rTests, err - } - - testJson, err := json.Marshal(testObj) - if err != nil { - return rTests, err - } - - var testCases []Case - err = json.Unmarshal(testJson, &testCases) - if err != nil { - rTests = make([]TestUnmarsh,0) - - var singleTest Case - err = json.Unmarshal(testJson, &singleTest) - if err != nil { - rTests = make([]TestUnmarsh,0) - err = nil - - var genJson []util.GenericJson - json.Unmarshal(testJson, &genJson) - for _,iv := range genJson{ - iTests, err := ats.unmarshalIntoTestCases(iv,loader,filepath.Join(ats.manifestDir, dir)) - if err != nil { - return rTests, err - } - rTests = append(rTests,iTests...) - } - - - }else { - rTests = append(rTests, TestUnmarsh{Case:singleTest,Path:dir}) - } - }else{ - for _,v := range testCases{ - rTests = append(rTests, TestUnmarsh{Case:v,Path:dir}) - } - } - - return -} -*/ func (ats Suite) loadManifest() (res []byte, err error) { loader := template.NewLoader(ats.datastore) manifestFile, err := filesystem.Fs.Open(ats.manifestPath) From ebb12e241612d6ec84a225b3d343f749766bc0e8 Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Mon, 11 Feb 2019 12:57:14 +0100 Subject: [PATCH 13/17] Remove runRequirements flag, as we do not have requirements anymore --- README.md | 1 - api_testsuite.go | 29 +++++++++++++---------------- main.go | 4 ---- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index fdce9b1..51dca38 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,6 @@ This starts the command with the following default settings: - `--directory testDirectory` or `-d testDirectory`: Defines which directory should be used for running the tests in it. The tool walks recursively trough all subdirectories and runs alls tests that have a "manifest.json" file in alphabetical order of the folder names. (Depth-First-Search) - `--single path/to/a/single/manifest.json` or `-s path/to/a/single/manifest.json`: Run only a single test. The path needs to point directly to the manifest file. (Not the directory containing it) -- `--no-requirements`: Do not run the requirements of the test suite. Useful for development of a test (e.g. To save time not doing a purge every time) ### Configure logging diff --git a/api_testsuite.go b/api_testsuite.go index 5f96c2e..f9eafb7 100644 --- a/api_testsuite.go +++ b/api_testsuite.go @@ -28,14 +28,13 @@ type Suite struct { StandardHeader map[string]*string `yaml:"header" json:"header"` StandardHeaderFromStore map[string]string `yaml:"header_from_store" json:"header_from_store"` - Config TestToolConfig - datastore *api.Datastore - manifestDir string - manifestPath string - reporter *report.Report - index int - executeRequirements bool - serverURL string + Config TestToolConfig + datastore *api.Datastore + manifestDir string + manifestPath string + reporter *report.Report + index int + serverURL string } // NewTestSuite creates a new suite on which we execute our tests on @@ -44,18 +43,16 @@ func NewTestSuite( config TestToolConfig, manifestPath string, r *report.Report, - executeRequirements bool, datastore *api.Datastore, index int, ) (suite Suite, err error) { suite = Suite{ - Config: config, - manifestDir: filepath.Dir(manifestPath), - manifestPath: manifestPath, - reporter: r, - executeRequirements: executeRequirements, - datastore: datastore, - index: index, + Config: config, + manifestDir: filepath.Dir(manifestPath), + manifestPath: manifestPath, + reporter: r, + datastore: datastore, + index: index, } manifest, err := suite.loadManifest() diff --git a/main.go b/main.go index 15969b2..bab7ea5 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,6 @@ import ( var ( reportFormat, reportFile string verbosity int - noRequirements bool rootDirectorys, singleTests []string ) @@ -91,8 +90,6 @@ func setup(ccmd *cobra.Command, args []string) { func runApiTests(cmd *cobra.Command, args []string) { - noRequirements = (cmd.Flag("no-requirements").Value.String() == "true") - //Check if paths are valid for _, rootDirectory := range rootDirectorys { @@ -130,7 +127,6 @@ func runApiTests(cmd *cobra.Command, args []string) { testToolConfig, manifestPath, r, - !noRequirements, datastore, 0, ) From b74d8607bc83760aa61262c179bbe8a241d0d421 Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 13 Feb 2019 09:10:13 +0100 Subject: [PATCH 14/17] Remove db-name config, as we do not need it anymore --- config.go | 5 +---- main.go | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index 3017328..a48d7f3 100644 --- a/config.go +++ b/config.go @@ -21,7 +21,6 @@ type FylrConfigStruct struct { } Apitest struct { Server string `mapstructure:"server"` - DBName string `mapstructure:"db-name"` StoreInit map[string]interface{} `mapstructure:"store"` Report struct { File string `mapstructure:"file"` @@ -66,16 +65,14 @@ func (config *FylrConfigStruct) SetLogVerbosity(verbosity int) { // TestToolConfig gives us the basic testtool infos type TestToolConfig struct { ServerURL string - DataBaseName string rootDirectorys []string TestDirectories []string } // NewTestToolConfig is mostly used for testing purpose. We can setup our config with this function -func NewTestToolConfig(serverURL, dataBaseName string, rootDirectory []string) (config TestToolConfig, err error) { +func NewTestToolConfig(serverURL string, rootDirectory []string) (config TestToolConfig, err error) { config = TestToolConfig{ ServerURL: serverURL, - DataBaseName: dataBaseName, rootDirectorys: rootDirectory, } err = config.extractTestDirectories() diff --git a/main.go b/main.go index bab7ea5..3e87bcd 100644 --- a/main.go +++ b/main.go @@ -104,12 +104,11 @@ func runApiTests(cmd *cobra.Command, args []string) { } serverUrl := FylrConfig.Apitest.Server - dbName := FylrConfig.Apitest.DBName reportFormat = FylrConfig.Apitest.Report.Format reportFile = FylrConfig.Apitest.Report.File //Save the config into TestToolConfig - testToolConfig, err := NewTestToolConfig(serverUrl, dbName, rootDirectorys) + testToolConfig, err := NewTestToolConfig(serverUrl, rootDirectorys) if err != nil { log.Fatal(err) } From 582bf79191ad6989154bd41f4977b6a3a3b7280e Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 13 Feb 2019 09:34:53 +0100 Subject: [PATCH 15/17] Adapt readme --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 51dca38..544b8c8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ ---- -menu: - main: - name: "apitest" - identifier: apitester - parent: "cli" ---- - # fylr apitest The fylr apitesting tool helps you to build automated apitests that can be run after every build to ensure a constant product quality. @@ -21,8 +13,7 @@ The report parametere of this config can be overwritten via a command line flag. ```yaml apitest: - server: "http://5.simon.pf-berlin.de" //The base url to the instance you want to fire the apitests against. Important: don’t add a trailing ‘/’ - db-name: "easy5-simon" //The name of your database. The tool uses this parameter to double check if you selected the right instance to prevent damage. + server: "http://5.simon.pf-berlin.de/api/v1" //The base url to the api you want to fire the apitests against. Important: don’t add a trailing ‘/’ report: //Configures the maschine report. For usage with jenkis or any other CI tool file: "apitest_report" //Filename of the report file. The file gets saved in the same directory of the fylr binary format: "json" //Format of the report. (Supported formats: json or junit) @@ -56,6 +47,7 @@ This starts the command with the following default settings: - `-v 1`: All from '-1' plus all responses - `-v 2`: All from '1' plus all requests + You can also set the log verbosity per single testcase. The greater verbosity wins. From c7d7e77fc32e22aee4278ef536134210681f1730 Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 13 Feb 2019 09:36:11 +0100 Subject: [PATCH 16/17] api_testsuite.go: Refactor for cleaner structure --- api_testcase.go | 2 +- api_testsuite.go | 183 +++++++++++++++++++++++++---------------------- main.go | 4 -- 3 files changed, 97 insertions(+), 92 deletions(-) diff --git a/api_testcase.go b/api_testcase.go index 73914b7..5c52e26 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -126,7 +126,7 @@ func (testCase Case) breakResponseIsPresent(request api.Request, response api.Re return false, fmt.Errorf("error matching break responses: %s", err) } - fmt.Println("breakResponseIsPresent: ", responsesMatch) + log.Tracef("breakResponseIsPresent: %s", responsesMatch) if responsesMatch.Equal { return true, nil diff --git a/api_testsuite.go b/api_testsuite.go index f9eafb7..bb82162 100644 --- a/api_testsuite.go +++ b/api_testsuite.go @@ -81,7 +81,15 @@ func (ats Suite) Run() (success bool) { r.SetTestCount(len(ats.Tests)) start := time.Now() - success = ats.run() + + success = true + for k, v := range ats.Tests { + sTestSuccess := ats.parseAndRunTest(v, ats.manifestDir, k) + if !sTestSuccess { + success = false + } + } + elapsed := time.Since(start) r.LeaveChild(success) @@ -93,124 +101,125 @@ func (ats Suite) Run() (success bool) { return } -func (ats Suite) run() (success bool) { - r := ats.reporter - - loader := template.NewLoader(ats.datastore) - - for k, v := range ats.Tests { - - tests, err := ats.loadTestCaseByte(v, ats.manifestDir, loader) - if err != nil { - r.SaveToReportLog(err.Error()) - log.Error(fmt.Errorf("can not loadTestCaseByte %s", err)) - return false - } - - for _, testContainer := range tests { - var test Case - err := json.Unmarshal(testContainer.CaseByte, &test) - if err != nil { - r.SaveToReportLog(err.Error()) - log.Error(fmt.Errorf("can not unmarshal single test %s", err)) - return false - } - - test.loader = loader - test.manifestDir = testContainer.Path - test.reporter = r - test.suiteIndex = ats.index - test.index = k - test.dataStore = ats.datastore - test.ServerURL = ats.Config.ServerURL - test.standardHeader = ats.StandardHeader - test.standardHeaderFromStore = ats.StandardHeaderFromStore - - success := test.runAPITestCase() - - if !success && !test.ContinueOnFailure { - return false - } - - } - } - - return true -} - -type TestUnmarsh struct { +type TestContainer struct { CaseByte json.RawMessage Path string } -func (ats Suite) loadTestCaseByte(v util.GenericJson, manifestDir string, loader template.Loader) ([]TestUnmarsh, error) { - rTests := make([]TestUnmarsh, 0) +func (ats Suite) parseAndRunTest(v util.GenericJson, manifestDir string, k int) bool { + //Init variables + r := ats.reporter + loader := template.NewLoader(ats.datastore) + //Get the Manifest with @ logic dir, testObj, err := template.LoadManifestDataAsRawJson(v, manifestDir) if err != nil { - return rTests, err + r.SaveToReportLog(err.Error()) + log.Error(fmt.Errorf("can not LoadManifestDataAsRawJson: %s", err)) + return false } + //Try to directly unmarshal the manifest into testcase array var testCases []json.RawMessage err = cjson.Unmarshal(testObj, &testCases) + if err == nil { + //Did work -> No go template + + var success bool + //Run single tests + for ki, v := range testCases { + + //Check if is @ and if so load the test + if rune(v[1]) == rune('@') { + var sS string + + err := cjson.Unmarshal(v, &sS) + if err != nil { + r.SaveToReportLog(err.Error()) + log.Error(fmt.Errorf("can not unmarshal: %s", err)) + return false + } + + success = ats.parseAndRunTest(sS, filepath.Join(manifestDir, dir), k+ki) + } else { + success = ats.runSingleTest(TestContainer{CaseByte: v, Path: filepath.Join(manifestDir, dir)}, r, loader, ki) + } - if err != nil { - err = nil - - rTests = make([]TestUnmarsh, 0) + if !success { + return false + } + } + } else { + //We were not able unmarshal into array, so we try to unmarshal into raw message var singleTest json.RawMessage err = cjson.Unmarshal(testObj, &singleTest) if err == nil { - rTests = append(rTests, TestUnmarsh{CaseByte: singleTest, Path: filepath.Join(manifestDir, dir)}) + //Did work to unmarshal -> no go template + + //Check if is @ and if so load the test + if rune(testObj[1]) == rune('@') { + var sS string + + err := cjson.Unmarshal(testObj, &sS) + if err != nil { + r.SaveToReportLog(err.Error()) + log.Error(fmt.Errorf("can not unmarshal: %s", err)) + return false + } + + return ats.parseAndRunTest(sS, filepath.Join(manifestDir, dir), k) + } else { + return ats.runSingleTest(TestContainer{CaseByte: testObj, Path: filepath.Join(manifestDir, dir)}, r, loader, k) + } } else { + //Did not work -> Could be go template or a mallformed json requestBytes, lErr := loader.Render(testObj, filepath.Join(manifestDir, dir), nil) if lErr != nil { - return rTests, lErr + return false } + //If the both objects are the same we did not have a template, but a mallformed json -> Call error if string(requestBytes) == string(testObj) { - return rTests, err - } - - tests, llErr := ats.loadTestCaseByte(requestBytes, filepath.Join(manifestDir, dir), loader) - if llErr != nil { - return rTests, llErr + r.SaveToReportLog(err.Error()) + log.Error(fmt.Errorf("can not unmarshal json: %s", err)) + return false } - rTests = append(rTests, tests...) - err = nil + //We have a template -> One level deeper with rendered bytes + return ats.parseAndRunTest([]byte(requestBytes), filepath.Join(manifestDir, dir), k) } - } else { - for _, v := range testCases { - rTests = append(rTests, TestUnmarsh{CaseByte: v, Path: filepath.Join(manifestDir, dir)}) - } } - tempTests := make([]TestUnmarsh, 0) + return true +} - for _, v := range rTests { +func (ats Suite) runSingleTest(tc TestContainer, r *report.Report, loader template.Loader, k int) (success bool) { + var test Case + jErr := cjson.Unmarshal(tc.CaseByte, &test) + if jErr != nil { + r.SaveToReportLog(jErr.Error()) + log.Error(fmt.Errorf("can not unmarshal single test %s", jErr)) + return false + } - if rune(v.CaseByte[1]) == rune('@') { - var sS string - cjson.Unmarshal(v.CaseByte, &sS) - tests, err := ats.loadTestCaseByte(sS, v.Path, loader) - if err != nil { - return rTests, fmt.Errorf("could not load inner loadTestCaseByte: %s", err) - } - tempTests = append(tempTests, tests...) - } else { - requestBytes, err := loader.Render(v.CaseByte, v.Path, nil) - if err != nil { - return rTests, fmt.Errorf("could not render: %s", err) - } - v.CaseByte = requestBytes + test.loader = loader + test.manifestDir = tc.Path + test.reporter = r + test.suiteIndex = ats.index + test.index = k + test.dataStore = ats.datastore + test.ServerURL = ats.Config.ServerURL + test.standardHeader = ats.StandardHeader + test.standardHeaderFromStore = ats.StandardHeaderFromStore - tempTests = append(tempTests, v) - } + success = test.runAPITestCase() + + if !success && !test.ContinueOnFailure { + return false } - return tempTests, err + return true } func (ats Suite) loadManifest() (res []byte, err error) { diff --git a/main.go b/main.go index 3e87bcd..214240b 100644 --- a/main.go +++ b/main.go @@ -32,10 +32,6 @@ func init() { &rootDirectorys, "directory", "d", []string{"."}, "path to directory containing the tests.") - TestCMD.PersistentFlags().Bool( - "no-requirements", false, - "don't run requirements for the testsuite.") - TestCMD.PersistentFlags().StringSliceVarP( &singleTests, "single", "s", []string{}, "path to a single manifest. Runs only that specified testsuite") From 954a35e2746db07f9987535d1fce7e6b94d6b841 Mon Sep 17 00:00:00 2001 From: Simon Frey Date: Wed, 13 Feb 2019 10:04:22 +0100 Subject: [PATCH 17/17] Go vet fixups --- api_testcase.go | 2 +- config_test.go | 4 ++-- lib/compare/comparer_test.go | 21 ++++++++++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/api_testcase.go b/api_testcase.go index 5c52e26..17b0de8 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -126,7 +126,7 @@ func (testCase Case) breakResponseIsPresent(request api.Request, response api.Re return false, fmt.Errorf("error matching break responses: %s", err) } - log.Tracef("breakResponseIsPresent: %s", responsesMatch) + log.Tracef("breakResponseIsPresent: %v", responsesMatch) if responsesMatch.Equal { return true, nil diff --git a/config_test.go b/config_test.go index 3f63452..dc571e6 100644 --- a/config_test.go +++ b/config_test.go @@ -53,11 +53,11 @@ func TestTestToolConfig_ExtractTestDirectories(t *testing.T) { SetupFS() //Invalid rootDirectory -> Expect error - _, err := NewTestToolConfig(server.URL+"/api/v1", "sTest", []string{"invalid"}) + _, err := NewTestToolConfig(server.URL+"/api/v1", []string{"invalid"}) test_utils.ExpectError(t, err, "NewTestToolConfig did not fail on invalid root directory") //Invalid rootDirectory -> Expect error - conf, err := NewTestToolConfig(server.URL+"/api/v1", "sTest", []string{"path"}) + conf, err := NewTestToolConfig(server.URL+"/api/v1", []string{"path"}) test_utils.CheckError(t, err, "NewTestToolConfig did fail on valid root directory") expectedResults := []string{ diff --git a/lib/compare/comparer_test.go b/lib/compare/comparer_test.go index 667dfb2..4cd1e1c 100644 --- a/lib/compare/comparer_test.go +++ b/lib/compare/comparer_test.go @@ -12,77 +12,91 @@ var trivialComparerTestData = []struct { in2 string match bool name string + err error }{ { `{"1":[1,2,3]}`, `{"2":"a","1":[1,2,3]}`, true, - "Left is SubMap of Right"}, + "Left is SubMap of Right", + nil, + }, { `[1, 2, 3]`, `[1, 2, 3, 4]`, true, "Left is SubArray of Right", + nil, }, { `[1, 2, 3]`, `[1, 2]`, false, "Left is SuperArray of Right", + nil, }, { `1`, `[1, 2, 3, 4]`, false, "Different Types Number and Array", + nil, }, { `{"1": {"1": [1, 2, 3]}}`, `{"1": {"1": [1, 2]}}`, false, "Nested List is not contained", + nil, }, { `{"1": {"1": [1, 2, true]}}`, `{"2": "something", "1": {"1": [1, 2, true, false]}}`, true, "Nested Dicts and Arrays are contained", + nil, }, { `null`, `null`, true, "null should match null", + nil, }, { `["something"]`, `null`, false, "null should not match to array", + nil, }, { `[]`, `null`, false, "null should not match to empty array", + nil, }, { `[1, 2, {"1": null}]`, `[1, 2, {"1": null, "2": "a"}]`, true, "nested null should match to nested null", + nil, }, { `"a"`, `nil`, false, "string conversion fails", + nil, }, { `"a"`, `"b"`, false, "string conversion succeeds but comparison fails", + nil, }, { `[{"event":{"object_id":118}}] `, @@ -153,6 +167,7 @@ var trivialComparerTestData = []struct { `, true, "Match events", + nil, }, } @@ -163,8 +178,8 @@ func TestTrivialJsonComparer(t *testing.T) { cjson.Unmarshal([]byte(td.in1), &json1) cjson.Unmarshal([]byte(td.in2), &json2) tjcMatch, err := JsonEqual(json1, json2, ComparisonContext{}) - if err != nil { - t.Errorf("error occured") + if err != td.err { + t.Errorf("Error missmatch. Want '%s' != '%s' Got", td.err, err) } if !(td.match == tjcMatch.Equal) { t.Errorf("got %t, want %t", tjcMatch.Equal, td.match)