From 14d9dd0dba607a396bcb7323a9d09fbd8de02d04 Mon Sep 17 00:00:00 2001 From: sabevzenko Date: Thu, 27 Jun 2024 12:55:26 +0300 Subject: [PATCH] HTTP scenario var/header postprocessor use multiple pipes 803e9ad581cd4a1790bd6c50c41f58c27724f6ac --- .changes/v0.5.29.md | 3 + .mapping.json | 1 + CHANGELOG.md | 4 + cli/cli.go | 2 +- .../scenario/http/postprocessor/var_header.go | 21 +- .../http/postprocessor/var_header_test.go | 359 +++++++++--------- docs/eng/scenario-grpc-generator.md | 2 +- docs/eng/scenario-http-generator.md | 76 ++-- docs/rus/scenario-grpc-generator.md | 2 +- docs/rus/scenario-http-generator.md | 72 ++-- examples/http/server/server.go | 3 +- .../testdata/http_scenario/http_payload.hcl | 15 +- tests/http_scenario/testdata/http_payload.hcl | 18 +- 13 files changed, 327 insertions(+), 251 deletions(-) create mode 100644 .changes/v0.5.29.md diff --git a/.changes/v0.5.29.md b/.changes/v0.5.29.md new file mode 100644 index 000000000..bea0eac09 --- /dev/null +++ b/.changes/v0.5.29.md @@ -0,0 +1,3 @@ +## v0.5.29 - 2024-06-25 +### Added +* HTTP scenario var/header postprocessor use multiple pipes diff --git a/.mapping.json b/.mapping.json index 15e8ad0d8..4434395ce 100644 --- a/.mapping.json +++ b/.mapping.json @@ -26,6 +26,7 @@ ".changes/v0.5.26.md":"load/projects/pandora/.changes/v0.5.26.md", ".changes/v0.5.27.md":"load/projects/pandora/.changes/v0.5.27.md", ".changes/v0.5.28.md":"load/projects/pandora/.changes/v0.5.28.md", + ".changes/v0.5.29.md":"load/projects/pandora/.changes/v0.5.29.md", ".changie.yaml":"load/projects/pandora/.changie.yaml", ".github/actions/setup-yc/action.yml":"load/projects/pandora/.github/actions/setup-yc/action.yml", ".github/workflows/release.yml":"load/projects/pandora/.github/workflows/release.yml", diff --git a/CHANGELOG.md b/CHANGELOG.md index ef093dc65..15fdc93d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## v0.5.29 - 2024-06-25 +### Added +* HTTP scenario var/header postprocessor use multiple pipes + ## v0.5.28 - 2024-06-24 ### Changed * `discard_overflow` logic. Waiter wait 2 seconds sliding window before skip payload diff --git a/cli/cli.go b/cli/cli.go index 42dc6f3d7..9a0e82813 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -25,7 +25,7 @@ import ( "go.uber.org/zap/zapcore" ) -const Version = "0.5.28" +const Version = "0.5.29" const defaultConfigFile = "load" const stdinConfigSelector = "-" diff --git a/components/providers/scenario/http/postprocessor/var_header.go b/components/providers/scenario/http/postprocessor/var_header.go index 0746fe4c0..7a25a465b 100644 --- a/components/providers/scenario/http/postprocessor/var_header.go +++ b/components/providers/scenario/http/postprocessor/var_header.go @@ -46,15 +46,22 @@ func (p *VarHeaderPostprocessor) parseValue(v string) (value string, modifier fu if len(vals) == 1 { return vals[0], func(in string) string { return in }, nil } - if len(vals) > 2 { - return "", nil, fmt.Errorf("VarHeaderPostprocessor supports only one modifier yet") - } - modifier, err = p.parseModifier(vals[1]) - if err != nil { - return "", nil, fmt.Errorf("failed to parse modifier %s: %w", vals[1], err) + + value = vals[0] + modifier = func(in string) string { return in } + + for _, modStr := range vals[1:] { + mod, err := p.parseModifier(modStr) + if err != nil { + return "", nil, fmt.Errorf("failed to parse modifier %s: %w", modStr, err) + } + previousModifier := modifier + modifier = func(in string) string { + return mod(previousModifier(in)) + } } - return vals[0], modifier, nil + return value, modifier, nil } func (p *VarHeaderPostprocessor) parseModifier(s string) (func(in string) string, error) { diff --git a/components/providers/scenario/http/postprocessor/var_header_test.go b/components/providers/scenario/http/postprocessor/var_header_test.go index d45add8d7..0eb308c66 100644 --- a/components/providers/scenario/http/postprocessor/var_header_test.go +++ b/components/providers/scenario/http/postprocessor/var_header_test.go @@ -13,8 +13,8 @@ func TestVarHeaderPostprocessor_Process(t *testing.T) { name string mappings map[string]string respHeaders map[string]string - expectedMap map[string]any - expectErr bool + wantMap map[string]any + wantErr bool }{ { name: "No Headers", @@ -23,7 +23,7 @@ func TestVarHeaderPostprocessor_Process(t *testing.T) { "key2": "header2", }, respHeaders: map[string]string{}, - expectedMap: map[string]any{}, + wantMap: map[string]any{}, }, { name: "No Fields", @@ -31,7 +31,7 @@ func TestVarHeaderPostprocessor_Process(t *testing.T) { respHeaders: map[string]string{ "key1": "header1", "key2": "header2"}, - expectedMap: nil, + wantMap: nil, }, { name: "Error in Fields", @@ -39,8 +39,8 @@ func TestVarHeaderPostprocessor_Process(t *testing.T) { "key1": "header1||", }, respHeaders: map[string]string{}, - expectedMap: map[string]any{}, - expectErr: true, + wantMap: map[string]any{}, + wantErr: true, }, { name: "Headers Exist", @@ -49,18 +49,24 @@ func TestVarHeaderPostprocessor_Process(t *testing.T) { "key2": "header2|lower", "key3": "header3|upper", "key4": "header4|substr(1,3)", + "key5": "header5|lower|replace(s,x)|substr(1,3)", + "auth": "Authorization|lower|replace(=,)|substr(6)", }, respHeaders: map[string]string{ - "header1": "Value1", - "header2": "Value2", - "header3": "Value3", - "header4": "Value4", + "header1": "Value1", + "header2": "Value2", + "header3": "Value3", + "header4": "Value4", + "header5": "aSdFgHjKl", + "Authorization": "Basic Ym9zY236Ym9zY28=", }, - expectedMap: map[string]any{ + wantMap: map[string]any{ "key1": "Value1", "key2": "value2", "key3": "VALUE3", "key4": "al", + "key5": "xd", + "auth": "ym9zy236ym9zy28", }, }, } @@ -76,81 +82,79 @@ func TestVarHeaderPostprocessor_Process(t *testing.T) { } reqMap, err := p.Process(resp, nil) - if tt.expectErr { + if tt.wantErr { assert.Error(t, err) return } assert.NoError(t, err) - assert.Equal(t, tt.expectedMap, reqMap) + assert.Equal(t, tt.wantMap, reqMap) }) } } func TestVarHeaderPostprocessor_ParseValue(t *testing.T) { - tests := []struct { - name string - input string - modifierVal string - expectedValue string - expectedModifierVal string - expectedError error + name string + input string + wantVal string + wantValAfterModifier string + wantErr error }{ { - name: "No Modifier", - input: "hello", - modifierVal: "asdf", - expectedValue: "hello", - expectedModifierVal: "asdf", - expectedError: nil, + name: "No Modifier", + input: "hello", + wantVal: "hello", + wantValAfterModifier: "hello", + wantErr: nil, + }, + { + name: "Lowercase Modifier", + input: "fOOt|lower", + wantVal: "fOOt", + wantValAfterModifier: "foot", + wantErr: nil, }, { - name: "Lowercase Modifier", - input: "foo|lower", - modifierVal: "ASDF", - expectedValue: "foo", - expectedModifierVal: "asdf", - expectedError: nil, + name: "Uppercase Modifier", + input: "bar|upper", + wantVal: "bar", + wantValAfterModifier: "BAR", + wantErr: nil, }, { - name: "Uppercase Modifier", - input: "bar|upper", - modifierVal: "upper", - expectedValue: "bar", - expectedModifierVal: "UPPER", - expectedError: nil, + name: "Substring Modifier", + input: "asdfghjkl|substr(1,3)", + wantVal: "asdfghjkl", + wantValAfterModifier: "sd", + wantErr: nil, }, { - name: "Substring Modifier", - input: "baz|substr(1,3)", - modifierVal: "asdfghjkl", - expectedValue: "baz", - expectedModifierVal: "sd", - expectedError: nil, + name: "Multiple Modifiers", + input: "aSdFgHjKl|lower|replace(s,x)|substr(1,3)", + wantVal: "aSdFgHjKl", + wantValAfterModifier: "xd", + wantErr: nil, }, { - name: "Multiple Modifiers", - input: "test|lower|upper", - modifierVal: "lower|upper", - expectedValue: "", // The method should return an empty string when multiple modifiers are provided. - expectedModifierVal: "", - expectedError: fmt.Errorf("VarHeaderPostprocessor supports only one modifier yet"), + name: "Multiple Modifiers 2", + input: "aPPliCation-JSONbro|lower|replace(-, /)|substr(0, 16)", + wantVal: "aPPliCation-JSONbro", + wantValAfterModifier: "application/json", + wantErr: nil, }, { - name: "Invalid Modifier", - input: "invalid|unknown", - modifierVal: "unknown", - expectedValue: "", // The method should return an empty string when the modifier is unknown. - expectedModifierVal: "", - expectedError: fmt.Errorf("failed to parse modifier unknown: unknown modifier unknown"), + name: "Invalid Modifier", + input: "invalid|unknown", + wantVal: "", // The method should return an empty string when the modifier is unknown. + wantValAfterModifier: "", + wantErr: fmt.Errorf("failed to parse modifier unknown: unknown modifier unknown"), }, { - name: "Invalid Modifier Arguments", - input: "invalid|substr(abc)", - modifierVal: "substr(abc)", - expectedValue: "", // The method should return an empty string when the modifier arguments are invalid. - expectedModifierVal: "", - expectedError: fmt.Errorf("failed to parse modifier substr(abc): substr modifier requires integer as first argument, got abc"), + name: "Invalid Modifier Arguments", + input: "invalid|substr(abc)", + wantVal: "", // The method should return an empty string when the modifier arguments are invalid. + wantValAfterModifier: "", + wantErr: fmt.Errorf("failed to parse modifier substr(abc): substr modifier requires integer as first argument, got abc"), }, } @@ -159,15 +163,15 @@ func TestVarHeaderPostprocessor_ParseValue(t *testing.T) { p := &VarHeaderPostprocessor{} value, modifier, err := p.parseValue(tt.input) if err != nil { - assert.EqualError(t, err, tt.expectedError.Error()) + assert.EqualError(t, err, tt.wantErr.Error()) return } assert.NoError(t, err) - assert.Equal(t, tt.expectedValue, value) + assert.Equal(t, tt.wantVal, value) - gotModifierVal := modifier(tt.modifierVal) - assert.Equal(t, tt.expectedModifierVal, gotModifierVal) + gotModifierVal := modifier(value) + assert.Equal(t, tt.wantValAfterModifier, gotModifierVal) }) } @@ -177,81 +181,81 @@ func TestVarHeaderPostprocessor_ParseModifier(t *testing.T) { p := &VarHeaderPostprocessor{} tests := []struct { - name string - input string - value string - expectedRes string - expectedError error + name string + input string + value string + want string + wantErr error }{ { - name: "Lowercase Modifier", - input: "lower", - value: "HELLO", - expectedRes: "hello", - expectedError: nil, + name: "Lowercase Modifier", + input: "lower", + value: "HELLO", + want: "hello", + wantErr: nil, }, { - name: "Uppercase Modifier", - input: "upper", - value: "world", - expectedRes: "WORLD", - expectedError: nil, + name: "Uppercase Modifier", + input: "upper", + value: "world", + want: "WORLD", + wantErr: nil, }, { - name: "Substring Modifier - Normal Case", - input: "substr(1,4)", - value: "abcdefgh", - expectedRes: "bcd", - expectedError: nil, + name: "Substring Modifier - Normal Case", + input: "substr(1,4)", + value: "abcdefgh", + want: "bcd", + wantErr: nil, }, { - name: "Substring Modifier - Start Index Out of Range (Negative)", - input: "substr(-2,4)", - value: "abcdefgh", - expectedRes: "ef", - expectedError: nil, + name: "Substring Modifier - Start Index Out of Range (Negative)", + input: "substr(-2,4)", + value: "abcdefgh", + want: "ef", + wantErr: nil, }, { - name: "Substring Modifier - Start Index Greater Than End Index", - input: "substr(5,3)", - value: "abcdefgh", - expectedRes: "de", - expectedError: nil, + name: "Substring Modifier - Start Index Greater Than End Index", + input: "substr(5,3)", + value: "abcdefgh", + want: "de", + wantErr: nil, }, { - name: "Substring Modifier - End Index Beyond Length", - input: "substr(2,100)", - value: "abcdefgh", - expectedRes: "cdefgh", // End index is beyond the length of the input value, so the modifier should return the substring from index 2 to the end. - expectedError: nil, + name: "Substring Modifier - End Index Beyond Length", + input: "substr(2,100)", + value: "abcdefgh", + want: "cdefgh", // End index is beyond the length of the input value, so the modifier should return the substring from index 2 to the end. + wantErr: nil, }, { - name: "Replace Modifier", - input: "replace(a,x)", - value: "banana", - expectedRes: "bxnxnx", - expectedError: nil, + name: "Replace Modifier", + input: "replace(a,x)", + value: "banana", + want: "bxnxnx", + wantErr: nil, }, { - name: "Invalid Modifier", - input: "invalid", - value: "test", - expectedRes: "", // The modFunc will be nil, so expectedRes should be an empty string. - expectedError: fmt.Errorf("unknown modifier invalid"), + name: "Invalid Modifier", + input: "invalid", + value: "test", + want: "", // The modFunc will be nil, so want should be an empty string. + wantErr: fmt.Errorf("unknown modifier invalid"), }, { - name: "Substring Modifier with Invalid Arguments", - input: "substr(2)", - value: "abc", - expectedRes: "c", - expectedError: nil, + name: "Substring Modifier with Invalid Arguments", + input: "substr(2)", + value: "abc", + want: "c", + wantErr: nil, }, { - name: "Replace Modifier with Invalid Arguments", - input: "replace(x)", - value: "abc", - expectedRes: "", // The modFunc will be nil, so expectedRes should be an empty string. - expectedError: fmt.Errorf("replace modifier requires 2 arguments"), + name: "Replace Modifier with Invalid Arguments", + input: "replace(x)", + value: "abc", + want: "", // The modFunc will be nil, so want should be an empty string. + wantErr: fmt.Errorf("replace modifier requires 2 arguments"), }, } @@ -262,13 +266,13 @@ func TestVarHeaderPostprocessor_ParseModifier(t *testing.T) { // If there is an error, modFunc should be nil, and the result should be an empty string. if err != nil { assert.Nil(t, modFunc) - assert.EqualError(t, err, tt.expectedError.Error()) + assert.EqualError(t, err, tt.wantErr.Error()) return } // If there is no error, apply the modFunc and check the result. res := modFunc(tt.value) - assert.Equal(t, tt.expectedRes, res) + assert.Equal(t, tt.want, res) assert.NoError(t, err) }) } @@ -276,74 +280,74 @@ func TestVarHeaderPostprocessor_ParseModifier(t *testing.T) { func TestVarHeaderPostprocessor_Substr(t *testing.T) { tests := []struct { - name string - args []string - value string - expectedRes string - expectedError error + name string + args []string + value string + want string + wantErr error }{ { - name: "Substring Modifier - Normal Case", - args: []string{"1", "4"}, - value: "abcdefgh", - expectedRes: "bcd", - expectedError: nil, + name: "Substring Modifier - Normal Case", + args: []string{"1", "4"}, + value: "abcdefgh", + want: "bcd", + wantErr: nil, }, { - name: "Substring Modifier - Start Index Out of Range (Negative)", - args: []string{"-2", "4"}, - value: "abcdefgh", - expectedRes: "ef", // Start index is negative, so it should count from the end of the string. - expectedError: nil, + name: "Substring Modifier - Start Index Out of Range (Negative)", + args: []string{"-2", "4"}, + value: "abcdefgh", + want: "ef", // Start index is negative, so it should count from the end of the string. + wantErr: nil, }, { - name: "Substring Modifier - End Index Out of Range (Negative)", - args: []string{"1", "-2"}, - value: "abcdefgh", - expectedRes: "bcdef", // End index is negative, so it should count from the end of the string. - expectedError: nil, + name: "Substring Modifier - End Index Out of Range (Negative)", + args: []string{"1", "-2"}, + value: "abcdefgh", + want: "bcdef", // End index is negative, so it should count from the end of the string. + wantErr: nil, }, { - name: "Substring Modifier - Start Index Greater Than End Index", - args: []string{"5", "3"}, - value: "abcdefgh", - expectedRes: "de", // Start index is greater than end index, so the modifier should return the substring from index 3 to 5. - expectedError: nil, + name: "Substring Modifier - Start Index Greater Than End Index", + args: []string{"5", "3"}, + value: "abcdefgh", + want: "de", // Start index is greater than end index, so the modifier should return the substring from index 3 to 5. + wantErr: nil, }, { - name: "Substring Modifier - End Index Beyond Length", - args: []string{"2", "100"}, - value: "abcdefgh", - expectedRes: "cdefgh", // End index is beyond the length of the input value, so the modifier should return the substring from index 2 to the end. - expectedError: nil, + name: "Substring Modifier - End Index Beyond Length", + args: []string{"2", "100"}, + value: "abcdefgh", + want: "cdefgh", // End index is beyond the length of the input value, so the modifier should return the substring from index 2 to the end. + wantErr: nil, }, { - name: "Substring Modifier with Invalid Arguments", - args: []string{"2"}, - value: "abc", - expectedRes: "c", - expectedError: nil, + name: "Substring Modifier with Invalid Arguments", + args: []string{"2"}, + value: "abc", + want: "c", + wantErr: nil, }, { - name: "Substring Modifier with Empty Arguments", - args: []string{}, - value: "abc", - expectedRes: "", // The modFunc will be nil, so expectedRes should be an empty string. - expectedError: fmt.Errorf("substr modifier requires one or two arguments"), + name: "Substring Modifier with Empty Arguments", + args: []string{}, + value: "abc", + want: "", // The modFunc will be nil, so want should be an empty string. + wantErr: fmt.Errorf("substr modifier requires one or two arguments"), }, { - name: "Substring Modifier with Non-Integer Arguments", - args: []string{"abc", "xyz"}, - value: "abc", - expectedRes: "", // The modFunc will be nil, so expectedRes should be an empty string. - expectedError: fmt.Errorf("substr modifier requires integer as first argument, got abc"), + name: "Substring Modifier with Non-Integer Arguments", + args: []string{"abc", "xyz"}, + value: "abc", + want: "", // The modFunc will be nil, so want should be an empty string. + wantErr: fmt.Errorf("substr modifier requires integer as first argument, got abc"), }, { - name: "Substring Modifier with Non-Integer second Argument", - args: []string{"1", "xyz"}, - value: "abc", - expectedRes: "", // The modFunc will be nil, so expectedRes should be an empty string. - expectedError: fmt.Errorf("substr modifier requires integer as second argument, got xyz"), + name: "Substring Modifier with Non-Integer second Argument", + args: []string{"1", "xyz"}, + value: "abc", + want: "", // The modFunc will be nil, so want should be an empty string. + wantErr: fmt.Errorf("substr modifier requires integer as second argument, got xyz"), }, } @@ -351,18 +355,13 @@ func TestVarHeaderPostprocessor_Substr(t *testing.T) { t.Run(tt.name, func(t *testing.T) { p := &VarHeaderPostprocessor{} modFunc, err := p.substr(tt.args) - - // If there is an error, modFunc should be nil, and the result should be an empty string. if err != nil { assert.Nil(t, modFunc) - assert.EqualError(t, err, tt.expectedError.Error()) + assert.EqualError(t, err, tt.wantErr.Error()) return } - - // If there is no error, apply the modFunc and check the result. - res := modFunc(tt.value) - assert.Equal(t, tt.expectedRes, res) assert.NoError(t, err) + assert.Equal(t, tt.want, modFunc(tt.value)) }) } } diff --git a/docs/eng/scenario-grpc-generator.md b/docs/eng/scenario-grpc-generator.md index cdc2add3e..3e222972d 100644 --- a/docs/eng/scenario-grpc-generator.md +++ b/docs/eng/scenario-grpc-generator.md @@ -317,7 +317,7 @@ Follow - [Variable sources](scenario/variable_source.md) # References -- [HTTP generator](http-generator.md) +- [gRPC generator](grpc-generator.md) - Best practices - [RPS per instance](best_practices/rps-per-instance.md) - [Shared client](best_practices/shared-client.md) diff --git a/docs/eng/scenario-http-generator.md b/docs/eng/scenario-http-generator.md index 1b458282b..984f66744 100644 --- a/docs/eng/scenario-http-generator.md +++ b/docs/eng/scenario-http-generator.md @@ -297,9 +297,11 @@ For more details about randomization functions, see [more](scenario/functions.md HCL example ```terraform -postprocessor "var/jsonpath" { - mapping = { - token = "$.auth_key" +request "your_request_name" { + postprocessor "var/jsonpath" { + mapping = { + token = "$.auth_key" + } } } ``` @@ -307,33 +309,61 @@ postprocessor "var/jsonpath" { ##### var/xpath ```terraform -postprocessor "var/xpath" { - mapping = { - data = "//div[@class='data']" +request "your_request_name" { + postprocessor "var/xpath" { + mapping = { + data = "//div[@class='data']" + } } } ``` ##### var/header -Creates a new variable from response headers +Creates a new variable from the response headers. -It is possible to specify simple string manipulations via pipe +It is possible to specify simple string manipulations via pipe. +Modifiers: - lower - upper -- substr(from, length) +- substr(from, length) - where `length` is optional - replace(search, replace) +Modifiers can be chained. + +**Example:** + +If you get a response with the headers +```http request +X-Trace-ID: we1fswe284awsfewf +Authorization: Basic Ym9zY236Ym9zY28= +``` + +And you need to save for future use `traceID=we1fswe284awsfewf` & `auth=ym9zy236ym9zy28` + +you can use the postprocessor with the modifiers + ```terraform -postprocessor "var/header" { - mapping = { - ContentType = "Content-Type|upper" - httpAuthorization = "Http-Authorization" +request "your_request_name" { + postprocessor "var/header" { + mapping = { + traceID = "X-Trace-ID" + auth = "Authorization|lower|replace(=,)|substr(6)" + } } } ``` +In templates you can use the result of this postprocessor as + +Translated with DeepL.com (free version) +```gotemplate +`{% raw %}{{.request.your_request_name.postprocessor.auth}}{% endraw %}` +`{% raw %}{{.request.your_request_name.postprocessor.traceID}}{% endraw %}` +``` + + ##### assert/response Checks header and body content @@ -341,16 +371,18 @@ Checks header and body content Upon assertion, further scenario execution is dropped ```terraform -postprocessor "assert/response" { - headers = { - "Content-Type" = "application/json" - } - body = ["token"] - status_code = 200 +request "your_request_name" { + postprocessor "assert/response" { + headers = { + "Content-Type" = "application/json" + } + body = ["token"] + status_code = 200 - size { - val = 10000 - op = ">" + size { + val = 10000 + op = ">" + } } } ``` diff --git a/docs/rus/scenario-grpc-generator.md b/docs/rus/scenario-grpc-generator.md index 3032476e4..c387af08a 100644 --- a/docs/rus/scenario-grpc-generator.md +++ b/docs/rus/scenario-grpc-generator.md @@ -319,7 +319,7 @@ scenario "scenario_name" { # Смотри так же -- [HTTP генератор](http-generator.md) +- [gRPC генератор](grpc-generator.md) - Практики использования - [RPS на инстанс](best_practices/rps-per-instance.md) - [Общий транспорт](best_practices/shared-client.md) diff --git a/docs/rus/scenario-http-generator.md b/docs/rus/scenario-http-generator.md index bc1d325f0..7339b2e2b 100644 --- a/docs/rus/scenario-http-generator.md +++ b/docs/rus/scenario-http-generator.md @@ -300,9 +300,11 @@ request "req_name" { Пример hcl ```terraform -postprocessor "var/jsonpath" { - mapping = { - token = "$.auth_key" +request "your_request_name" { + postprocessor "var/jsonpath" { + mapping = { + token = "$.auth_key" + } } } ``` @@ -310,33 +312,57 @@ postprocessor "var/jsonpath" { ##### var/xpath ```terraform -postprocessor "var/xpath" { - mapping = { - data = "//div[@class='data']" +request "your_request_name" { + postprocessor "var/xpath" { + mapping = { + data = "//div[@class='data']" + } } } ``` ##### var/header -Создает новую переменную из заголовков ответа +Создает новую переменную из заголовков ответа. -Есть возможность через pipe указывать простейшие строковые манипуляции +Есть возможность через pipe указывать простейшие строковые манипуляции. +Модификаторы: - lower - upper -- substr(from, length) +- substr(from, length) - где `length` - опционально - replace(search, replace) +Модификаторы можно выстраивать в цепочку. + +**Пример:** + +Если вам приходит ответ с заголовками +```http request +X-Trace-ID: we1fswe284awsfewf +Authorization: Basic Ym9zY236Ym9zY28= +``` + +И вам требуется сохранить для дальнейшего использования `traceID=we1fswe284awsfewf` & `auth=ym9zy236ym9zy28` +вы можете использовать постпроцессор с модификаторами + ```terraform -postprocessor "var/header" { - mapping = { - ContentType = "Content-Type|upper" - httpAuthorization = "Http-Authorization" +request "your_request_name" { + postprocessor "var/header" { + mapping = { + traceID = "X-Trace-ID" + auth = "Authorization|lower|replace(=,)|substr(6)" + } } } ``` +В шаблонах вы можете использовать результать данного постпроцессора как +```gotemplate +`{% raw %}{{.request.your_request_name.postprocessor.auth}}{% endraw %}` +`{% raw %}{{.request.your_request_name.postprocessor.traceID}}{% endraw %}` +``` + ##### assert/response Проверяет значения заголовков и тела @@ -344,16 +370,18 @@ postprocessor "var/header" { Если матчинг не срабатывает, прекращает дальнейшее выполнение сценария ```terraform -postprocessor "assert/response" { - headers = { - "Content-Type" = "application/json" - } - body = ["token"] - status_code = 200 +request "your_request_name" { + postprocessor "assert/response" { + headers = { + "Content-Type" = "application/json" + } + body = ["token"] + status_code = 200 - size { - val = 10000 - op = ">" + size { + val = 10000 + op = ">" + } } } ``` diff --git a/examples/http/server/server.go b/examples/http/server/server.go index 655853256..22fd6adff 100644 --- a/examples/http/server/server.go +++ b/examples/http/server/server.go @@ -108,7 +108,8 @@ func (s *Server) authHandler(w http.ResponseWriter, r *http.Request) { s.mu.RUnlock() w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(fmt.Sprintf(`{"auth_key": "%s"}`, authKey))) + w.Header().Set("Authorization", "Bearer "+authKey) + _, _ = w.Write([]byte(`{"result":"ok"}`)) } func (s *Server) listHandler(w http.ResponseWriter, r *http.Request) { diff --git a/tests/acceptance/testdata/http_scenario/http_payload.hcl b/tests/acceptance/testdata/http_scenario/http_payload.hcl index 596cbf0b0..7fc103d54 100644 --- a/tests/acceptance/testdata/http_scenario/http_payload.hcl +++ b/tests/acceptance/testdata/http_scenario/http_payload.hcl @@ -39,31 +39,32 @@ EOF mapping = { Content-Type = "Content-Type|upper" httpAuthorization = "Http-Authorization" + auth = "Authorization|substr(7)" } } postprocessor "var/jsonpath" { mapping = { - token = "$.auth_key" + result = "$.result" } } postprocessor "assert/response" { headers = { Content-Type = "json" } - body = ["key"] + body = ["{\"result\":\"ok\"}"] size { - val = 40 op = ">" + val = 10 } } postprocessor "assert/response" { - body = ["auth"] + body = ["result"] } } request "list_req" { method = "GET" headers = { - Authorization = "Bearer {{.request.auth_req.postprocessor.token}}" + Authorization = "Bearer {{.request.auth_req.postprocessor.auth}}" Content-Type = "application/json" Useragent = "Yandex" } @@ -81,7 +82,7 @@ request "order_req" { method = "POST" uri = "/order" headers = { - Authorization = "Bearer {{.request.auth_req.postprocessor.token}}" + Authorization = "Bearer {{.request.auth_req.postprocessor.auth}}" Content-Type = "application/json" Useragent = "Yandex" } @@ -101,7 +102,7 @@ request "order_req2" { method = "POST" uri = "/order" headers = { - Authorization = "Bearer {{.request.auth_req.postprocessor.token}}" + Authorization = "Bearer {{.request.auth_req.postprocessor.auth}}" Content-Type = "application/json" Useragent = "Yandex" } diff --git a/tests/http_scenario/testdata/http_payload.hcl b/tests/http_scenario/testdata/http_payload.hcl index a031697de..042f5f59d 100644 --- a/tests/http_scenario/testdata/http_payload.hcl +++ b/tests/http_scenario/testdata/http_payload.hcl @@ -37,33 +37,33 @@ EOF } postprocessor "var/header" { mapping = { - Content-Type = "Content-Type|upper" - httpAuthorization = "Http-Authorization" + Content-Type = "Content-Type|upper" + auth = "Authorization|substr(7)" } } postprocessor "var/jsonpath" { mapping = { - token = "$.auth_key" + result = "$.result" } } postprocessor "assert/response" { headers = { Content-Type = "json" } - body = ["key"] + body = ["{\"result\":\"ok\"}"] size { - val = 40 op = ">" + val = 10 } } postprocessor "assert/response" { - body = ["auth"] + body = ["result"] } } request "list_req" { method = "GET" headers = { - Authorization = "Bearer {{.request.auth_req.postprocessor.token}}" + Authorization = "Bearer {{.request.auth_req.postprocessor.auth}}" Content-Type = "application/json" Useragent = "Yandex" } @@ -81,7 +81,7 @@ request "order_req" { method = "POST" uri = "/order" headers = { - Authorization = "Bearer {{.request.auth_req.postprocessor.token}}" + Authorization = "Bearer {{.request.auth_req.postprocessor.auth}}" Content-Type = "application/json" Useragent = "Yandex" } @@ -101,7 +101,7 @@ request "order_req2" { method = "POST" uri = "/order" headers = { - Authorization = "Bearer {{.request.auth_req.postprocessor.token}}" + Authorization = "Bearer {{.request.auth_req.postprocessor.auth}}" Content-Type = "application/json" Useragent = "Yandex" }