diff --git a/steps/http/session.go b/steps/http/session.go index 17254308..101834ce 100755 --- a/steps/http/session.go +++ b/steps/http/session.go @@ -19,13 +19,13 @@ import ( "context" "errors" "fmt" - "strings" "io/ioutil" "net" "net/http" "net/url" - "time" "path" + "strings" + "time" "github.com/Telefonica/golium" "github.com/google/uuid" @@ -35,8 +35,8 @@ import ( const ( slash string = "/" - ) - +) + // Request information of the Session. type Request struct { // Endpoint of the HTTP server. It might include a base path. @@ -82,7 +82,7 @@ func (s *Session) URL() (*url.URL, error) { } u.Path = path.Join(u.Path, s.Request.Path) /* - * NOTE: path.Join removes trailing slash using Clean thus, + * NOTE: path.Join removes trailing slash using Clean thus, * we need to add it if is in s.Request.Path * - Reference: https://forum.golangbridge.org/t/how-to-concatenate-paths-for-api-request/5791 * - Docs: https://pkg.go.dev/path#Join @@ -259,6 +259,16 @@ func (s *Session) ValidateResponseHeaders(ctx context.Context, expectedHeaders m return nil } +// ValidateNotResponseHeaders checks that a set of response headers are not included in HTTP response. +func (s *Session) ValidateNotResponseHeaders(ctx context.Context, expectedHeaders []string) error { + for _, expectedHeader := range expectedHeaders { + if len(s.Response.Response.Header.Values(expectedHeader)) > 0 { + return fmt.Errorf("HTTP response includes the header '%s'", expectedHeader) + } + } + return nil +} + // ValidateResponseBodyJSONSchema validates the response body against the JSON schema. func (s *Session) ValidateResponseBodyJSONSchema(ctx context.Context, schema string) error { schemasDir := golium.GetConfig().Dir.Schemas diff --git a/steps/http/steps.go b/steps/http/steps.go index 2ca892fc..038f68a0 100755 --- a/steps/http/steps.go +++ b/steps/http/steps.go @@ -100,6 +100,13 @@ func (s Steps) InitializeSteps(ctx context.Context, scenCtx *godog.ScenarioConte } return session.ValidateResponseHeaders(ctx, headers) }) + scenCtx.Step(`^the HTTP response must not contain the headers$`, func(t *godog.Table) error { + headers, err := golium.ConvertTableColumnToArray(ctx, t) + if err != nil { + return fmt.Errorf("failed processing HTTP headers from table: %w", err) + } + return session.ValidateNotResponseHeaders(ctx, headers) + }) scenCtx.Step(`^the HTTP response body must comply with the JSON schema "([^"]*)"$`, func(schema string) error { return session.ValidateResponseBodyJSONSchema(ctx, golium.ValueAsString(ctx, schema)) }) diff --git a/steps/s3/session.go b/steps/s3/session.go index 6c899404..fcc0fd19 100755 --- a/steps/s3/session.go +++ b/steps/s3/session.go @@ -30,9 +30,14 @@ import ( type Session struct { Client *aws_s.Session + CreatedBuckets []*CreatedBucket CreatedDocuments []*CreatedDocument } +type CreatedBucket struct { + bucket string +} + type CreatedDocument struct { bucket string key string @@ -76,7 +81,6 @@ func (s *Session) UploadS3FileWithContent(ctx context.Context, bucket, key, mess Body: strings.NewReader(message), }) if err != nil { - logger.LogMessage("unable to upload file") return fmt.Errorf("unable to upload %q to %q, %v", key, bucket, err) } @@ -95,8 +99,27 @@ func (s *Session) CreateS3Bucket(ctx context.Context, bucket string) error { s3Client := s3.New(s.Client) if _, err := s3Client.CreateBucket(cparams); err != nil { - logger.LogMessage(fmt.Sprintf("error creating a new bucket: %s, err: %v", bucket, err)) + return fmt.Errorf("error creating a new bucket: %s, err: %v", bucket, err) + } + + s.CreatedBuckets = append(s.CreatedBuckets, &CreatedBucket{bucket: bucket}) + return nil +} + +// DeleteS3Bucket deletes the bucket in S3. +func (s *Session) DeleteS3Bucket(ctx context.Context, bucket string) error { + logger := GetLogger() + logger.LogMessage(fmt.Sprintf("deleting bucket: %s", bucket)) + cparams := &s3.DeleteBucketInput{ + Bucket: aws.String(bucket), + } + + s3Client := s3.New(s.Client) + + if _, err := s3Client.DeleteBucket(cparams); err != nil { + return fmt.Errorf("error deleting bucket: %s, err: %v", bucket, err) } + return nil } @@ -110,7 +133,7 @@ func (s *Session) ValidateS3BucketExists(ctx context.Context, bucket string) err Bucket: aws.String(bucket), } if _, err := s3svc.GetBucketLocation(input); err != nil { - return fmt.Errorf("bucket: '%s' does not exists", bucket) + return fmt.Errorf("bucket: '%s' does not exist", bucket) } return nil } @@ -125,7 +148,7 @@ func (s *Session) ValidateS3FileExists(ctx context.Context, bucket, key string) return err } if !exists { - return fmt.Errorf("failed validating s3 file exits: no file exists in bucket '%s' with name '%s' ", bucket, key) + return fmt.Errorf("failed validating s3 file exists: file '%s' does not exist in bucket '%s', err %v ", key, bucket, err) } return nil } @@ -189,10 +212,17 @@ func (s *Session) s3KeyExists(s3svc *s3.S3, bucket string, key string) (bool, er // CleanUp cleans session by deleting all documents created in S3 func (s *Session) CleanUp(ctx context.Context) { + logger := GetLogger() + // Remove keys for _, file := range s.CreatedDocuments { if err := s.DeleteS3File(ctx, file.bucket, file.key); err != nil { - logger := GetLogger() - logger.LogMessage(fmt.Sprintf("Failure on deletion of s3 file '%s' in bucket '%s', err %v", file.key, file.bucket, err)) + logger.LogMessage(fmt.Sprintf("failure on deletion of s3 file '%s' in bucket '%s', err %v", file.key, file.bucket, err)) + } + } + // Remove buckets + for _, file := range s.CreatedBuckets { + if err := s.DeleteS3Bucket(ctx, file.bucket); err != nil { + logger.LogMessage(fmt.Sprintf("failure on deletion of s3 bucket '%s', err %v", file.bucket, err)) } } } diff --git a/steps/s3/steps.go b/steps/s3/steps.go index aee77900..25779d04 100755 --- a/steps/s3/steps.go +++ b/steps/s3/steps.go @@ -38,19 +38,25 @@ func (cs Steps) InitializeSteps(ctx context.Context, scenCtx *godog.ScenarioCont }) scenCtx.Step(`^I create a file in S3 bucket "([^"]+)" with key "([^"]+)" and the content$`, func(bucket, key string, message *godog.DocString) error { if session.Client == nil { - return fmt.Errorf("failed creating S3 file: nil session: may forget step 'I create a new S3 session'") + return fmt.Errorf("failed creating S3 session: nil session: may forget step 'I create a new S3 session'") } return session.UploadS3FileWithContent(ctx, golium.ValueAsString(ctx, bucket), golium.ValueAsString(ctx, key), golium.ValueAsString(ctx, message.Content)) }) scenCtx.Step(`^I create the S3 bucket "([^"]+)"$`, func(bucket string) error { if session.Client == nil { - return fmt.Errorf("failed creating S3 file: nil session: may forget step 'I create a new S3 session'") + return fmt.Errorf("failed creating S3 session: nil session: may forget step 'I create a new S3 session'") } return session.CreateS3Bucket(ctx, golium.ValueAsString(ctx, bucket)) }) + scenCtx.Step(`^I delete the S3 bucket "([^"]+)"$`, func(bucket string) error { + if session.Client == nil { + return fmt.Errorf("failed creating S3 session: nil session: may forget step 'I create a new S3 session'") + } + return session.DeleteS3Bucket(ctx, golium.ValueAsString(ctx, bucket)) + }) scenCtx.Step(`^the S3 bucket "([^"]+)" exists$`, func(bucket string) error { if session.Client == nil { - return fmt.Errorf("failed creating S3 file: nil session: may forget step 'I create a new S3 session'") + return fmt.Errorf("failed creating S3 session: nil session: may forget step 'I create a new S3 session'") } return session.ValidateS3BucketExists(ctx, golium.ValueAsString(ctx, bucket)) }) diff --git a/table.go b/table.go index 2cac8772..d6125c27 100755 --- a/table.go +++ b/table.go @@ -45,6 +45,23 @@ func ConvertTableToMap(ctx context.Context, t *godog.Table) (map[string]interfac return m, nil } +// ConvertTableColumnToArray converts a godog table with 1 column into a []string. +func ConvertTableColumnToArray(ctx context.Context, t *godog.Table) ([]string, error) { + m := []string{} + if len(t.Rows) == 0 { + return m, nil + } + for i := 0; i < len(t.Rows); i++ { + cells := t.Rows[i].Cells + if len(cells) > 1 { + return m, errors.New("table must have 1 unique column") + } + propKey := cells[0].Value + m = append(m, propKey) + } + return m, nil +} + // ConvertTableToMultiMap converts a godog table with 2 columns into a map[string][]string. // The multimap is using url.Values. // The multimap is useful to support multiple values for the same key (e.g. for query parameters diff --git a/test/acceptance/environments/local.yml b/test/acceptance/environments/local.yml index 163ce5f6..9f9a136c 100755 --- a/test/acceptance/environments/local.yml +++ b/test/acceptance/environments/local.yml @@ -111,4 +111,4 @@ minio: true minioEndpoint: https://play.min.io:9000 minioAwsAccessKeyId: Q3AM3UQ867SPQQA43P2F minioAwsSecretAccessKey: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG -minioAwsRegion: eu-west-1 +minioAwsRegion: us-east-1 diff --git a/test/acceptance/features/http.feature b/test/acceptance/features/http.feature index b2082b5e..a3fd601d 100755 --- a/test/acceptance/features/http.feature +++ b/test/acceptance/features/http.feature @@ -162,3 +162,17 @@ Feature: HTTP client And the HTTP path "/jpeg/" When I send a HTTP "POST" request Then the HTTP status code must be "404" + + @http1 + Scenario: Send a GET request and check if a specific header is not in response headers + Given the HTTP endpoint "[CONF:url]/anything" + And the HTTP path "/test-query" + And the HTTP query parameters + | exists | true | + | sort | name | + And the HTTP request headers + | Authorization | Bearer access-token | + When I send a HTTP "GET" request + Then the HTTP status code must be "200" + And the HTTP response must not contain the headers + | non-existent-header | diff --git a/test/acceptance/features/s3.feature b/test/acceptance/features/s3.feature index cd872ba9..8d0446bf 100755 --- a/test/acceptance/features/s3.feature +++ b/test/acceptance/features/s3.feature @@ -21,3 +21,4 @@ Feature: S3 client Document content line 2 """ And I delete the file in S3 bucket "[CONF:s3Bucket]" with key "[CTXT:key]" + And I delete the S3 bucket "[CONF:s3Bucket]"