Skip to content

Commit

Permalink
add two new functions for dynamic headers (#31)
Browse files Browse the repository at this point in the history
* add two new functions for dynamic headers

* use named return values

* make function specific to url-encoded forms and allow for selecting arbitrary elements from the json

* Minor (#32)

* clean up isJSON funcs

* use external lib for json value retrieval

* simplify compacting json

* update dependencies

* handle empty string field and update readme

* fix whitespace in readme

* fix json array syntax

* handle non-json response bodies

Co-authored-by: Tony Li <[email protected]>
  • Loading branch information
rhennessy-nyt and tonglil authored Oct 16, 2020
1 parent 433c953 commit 4ddc601
Show file tree
Hide file tree
Showing 24 changed files with 5,204 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ steps:
GOOS: linux
CGO_ENABLED: 0
commands:
- go test
- go build
- go test ./...

- name: test
image: golang:alpine
Expand Down
28 changes: 27 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

169 changes: 134 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,92 @@ Args:
- passphrase (can be empty string if key is not encrypted)
- string(s)... (any amount of strings, from previously set headers or literal values)

#### postFormURLEncoded
Sends an HTTP POST request to the given URL (with Content-Type `application/x-www-form-urlencoded`), returns either the whole response body or a specific JSON element, and treats the remaining arguments as data

Args:
- url (the URL to send the request to)
- element (see section on "JSON Query Syntax" below)
- string(s)... (any amount of strings, from previously set headers or literal values, which will be concatenated and delimited with '&' for the request body)

##### JSON Query Syntax
The element to return from the JSON response body is specified by a dot-delimited string representing the path to the element. Each part of this string is a single step in that path, and array access is handled by providing a zero-based index. If the element selected has a primitive value, that value is returned. If the element selected is a JSON object or array, that object/array is returned in compact form (insignificant whitespace removed). To return the entire JSON response body, use an empty string.

**Examples:**
**Primitive data**
Response body:
```json
{
"nested": {
"object": {
"data": 123
}
}
}
```
Query: 'nested.object.data'
Result: 123

**Object**
Response body:
```json
{
"nested": {
"object": {
"data": 123
}
}
}
```
Query: 'nested.object'
Result: {"data":123}

**Array**
Response body:
```json
{
"nested": {
"array": [
"abc",
"def",
"ghi"
]
}
}
```
Query: 'nested.array'
Result: ["abc","def","ghi"]

**Array element**
Response body:
```json
{
"nested": {
"array": [
"abc",
"def",
"ghi"
]
}
}
```
Query: 'nested.array.1'
Result: def

**Whole response**
Response body:
```
Just a normal, non-JSON response
```
Query: ''
Result: Just a normal, non-JSON response

#### concat
Concatenates the arguments into a single string

Args:
- string(s)... (any amount of strings, from previously set headers or literal values, which will be concatenated)

### Full test example

Required fields for each test:
Expand All @@ -166,51 +252,64 @@ All other fields are optional. All matchings are case insensitive.

```yaml
tests:
- description: 'root' # Description, will be printed with test results. Required
conditions: # Specify conditions. Test only runs when all conditions are met
env: # Matches an environment variable
TEST_ENV: '^(dev|stg)$' # Environment variable name : regular expression
skipCertVerification: false # Set true to skip verification of server TLS certificate (insecure and not recommended)
request: # Request to send
scheme: 'https' # URL scheme. Only http and https are supported. Default: https
host: 'example.com' # Host to test against (this overrides TEST_HOST for this specific test)
method: 'POST' # HTTP method. Default: GET
path: '/' # Path to hit. Required
headers: # Headers
- description: 'root' # Description, will be printed with test results. Required
conditions: # Specify conditions. Test only runs when all conditions are met
env: # Matches an environment variable
TEST_ENV: '^(dev|stg)$' # Environment variable name : regular expression
skipCertVerification: false # Set true to skip verification of server TLS certificate (insecure and not recommended)
request: # Request to send
scheme: 'https' # URL scheme. Only http and https are supported. Default: https
host: 'example.com' # Host to test against (this overrides TEST_HOST for this specific test)
method: 'POST' # HTTP method. Default: GET
path: '/' # Path to hit. Required
headers: # Headers
x-test-header-0: 'abc'
x-test: '${REQ_TEST}' # Environment variable substitution
dynamicHeaders: # Headers whose values are determined at runtime (see "Dynamic Headers" section above)
- name: x-test-timestamp # Name of the header to set
function: now # Calling a no-arg function to get the header value
x-test: '${REQ_TEST}' # Environment variable substitution
dynamicHeaders: # Headers whose values are determined at runtime (see "Dynamic Headers" section above)
- name: x-test-timestamp # Name of the header to set
function: now # Calling a no-arg function to get the header value
- name: x-test-signature
function: signStringRS256PKCS8 # Function called to determine the header value
args: # List of arguments for the function (can be omitted for functions that don't take arguments)
- '${TEST_KEY}' # Can use environment variable substitution
function: signStringRS256PKCS8 # Function called to determine the header value
args: # List of arguments for the function (can be omitted for functions that don't take arguments)
- '${TEST_KEY}' # Can use environment variable substitution
- '${TEST_PASS}'
- x-test-timestamp # Can use values of previously set headers
- '/svc/login' # Can use literals
- x-test-timestamp # Can use values of previously set headers
- '/svc/login' # Can use literals
- x-test-header-0
- x-test
body: '' # Request body. Processed as string
response: # Expected response
statusCodes: [201] # List of expected response status codes
headers: # Expected response headers
patterns: # Match response header patterns
server: '^ECS$' # Header name : regular expression
- name: x-test-token
function: postFormURLEncoded # Function called to retrieve the header value from an API
args:
- '${TOKEN_URL}' # The URL to POST to
- 'results.7.token.id_token' # The dot-delimited string representing an element to return from the JSON response body; use integers for array elements or '' for the whole response body
- 'client_secret=${CLIENT_SECRET}' # Can use literals, environment variables, or a combination
- x-test-timestamp # Can use values of previously set headers
- 'client_id=123' # These arguments will be combined to send 'client_secret=${CLIENT_SECRET}&x-test-timestamp&client_id=123' in the body of the request
- name: authorization
function: concat # Function that concatenates strings, either literals or values from previously set headers
args:
- 'Bearer '
- x-test-token
body: '' # Request body. Processed as string
response: # Expected response
statusCodes: [201] # List of expected response status codes
headers: # Expected response headers
patterns: # Match response header patterns
server: '^ECS$' # Header name : regular expression
cache-control: '.+'
notPresent: # Specify headers not expected to exist.
- 'set-cookie' # These are not regular expressions
notPresent: # Specify headers not expected to exist.
- 'set-cookie' # These are not regular expressions
- 'x-frame-options'
notMatching:
set-cookie: ^.*abc.*$ # Specify headers expected to exist but NOT match the given regex
body: # Response body
patterns: # Response body has to match all patterns in this list in order to pass test
- 'charset="utf-8"' # Regular expressions
set-cookie: ^.*abc.*$ # Specify headers expected to exist but NOT match the given regex
body: # Response body
patterns: # Response body has to match all patterns in this list in order to pass test
- 'charset="utf-8"' # Regular expressions
- 'Example Domain'
- description: 'sign up page' # Second test
- description: 'sign up page' # Second test
request:
path: '/signup'
response:
Expand Down
2 changes: 2 additions & 0 deletions dynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ type resolveHeader func(existingHeaders map[string]string, args []string) (strin
var funcMap = map[string]resolveHeader{
"now": functions.Now,
"signStringRS256PKCS8": functions.SignStringRS256PKCS8,
"postFormURLEncoded": functions.PostFormURLEncoded,
"concat": functions.Concat,
}
20 changes: 20 additions & 0 deletions functions/concat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package functions

import (
"strings"
)

// Concat concatenates the args into a single string, substituting previously defined headers if available.
func Concat(existingHeaders map[string]string, args []string) (string, error) {
var buffer strings.Builder

for _, arg := range args {
if value, ok := existingHeaders[arg]; ok {
buffer.WriteString(value)
} else {
buffer.WriteString(arg)
}
}

return buffer.String(), nil
}
67 changes: 67 additions & 0 deletions functions/concat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package functions

import (
"testing"
)

func TestConcat(t *testing.T) {
var tests = []struct {
existingHeaders map[string]string
args []string
expected string
}{
{
map[string]string{},
[]string{""},
"",
},
{
map[string]string{},
[]string{"my string"},
"my string",
},
{
map[string]string{
"x-previous-header": "123",
},
[]string{""},
"",
},
{
map[string]string{
"x-previous-header": "123",
},
[]string{"x-previous-header"},
"123",
},
{
map[string]string{
"x-previous-header": "123",
},
[]string{"x-previous-header", "my string"},
"123my string",
},
{
map[string]string{
"x-previous-header1": "123",
"x-previous-header2": "456",
},
[]string{"x-previous-header1", "my string", "x-previous-header2"},
"123my string456",
},
{
map[string]string{
"x-previous-header1": "123",
},
[]string{"x-previous-header2", "my string"},
"x-previous-header2my string",
},
}

for _, tc := range tests {
actual, _ := Concat(tc.existingHeaders, tc.args)
if actual != tc.expected {
t.Errorf("Concat(%v, %v): expected %v, actual %v", tc.existingHeaders, tc.args, tc.expected, actual)
}
}
}
Loading

0 comments on commit 4ddc601

Please sign in to comment.