Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 33 additions & 9 deletions checks/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,40 @@ func truncateAndStringifyBody(body []byte) string {
}

func parseVariables(body []byte, vardefs []api.HTTPRequestResponseVariable, variables map[string]string) error {
bodyString := string(body)

for _, vardef := range vardefs {
vals, err := valsFromJqPath(vardef.Path, string(body))
if err != nil {
return err
}
if len(vals) != 1 || vals[0] == nil {
continue
switch {
case vardef.Path != "" && vardef.BodyRegex != "":
return fmt.Errorf("invalid response variable configuration")

case vardef.BodyRegex != "":
re, err := regexp.Compile(vardef.BodyRegex)
if err != nil {
return fmt.Errorf("invalid response body variable configuration")
}
if re.NumSubexp() != 1 {
return fmt.Errorf("invalid response body variable configuration")
}
Comment thread
theodore-s-beers marked this conversation as resolved.
matches := re.FindStringSubmatch(bodyString)
if len(matches) == 2 && matches[1] != "" {
variables[vardef.Name] = matches[1]
}

case vardef.Path != "":
vals, err := valsFromJqPath(vardef.Path, bodyString)
if err != nil {
return err
}
if len(vals) == 1 && vals[0] != nil {
variables[vardef.Name] = fmt.Sprintf("%v", vals[0])
}

default:
return fmt.Errorf("invalid response variable configuration")
}
variables[vardef.Name] = fmt.Sprintf("%v", vals[0])
}

return nil
}

Expand All @@ -223,10 +247,10 @@ func parseHeaderVariables(headers map[string]string, vardefs []api.HTTPRequestRe
if vardef.Regex != "" {
re, err := regexp.Compile(vardef.Regex)
if err != nil {
return err
return fmt.Errorf("invalid response header variable configuration")
}
if re.NumSubexp() != 1 {
return fmt.Errorf("regex for header variable %q must have exactly one capture group", vardef.Name)
return fmt.Errorf("invalid response header variable configuration")
}
Comment thread
theodore-s-beers marked this conversation as resolved.

matches := re.FindStringSubmatch(headerValue)
Expand Down
32 changes: 32 additions & 0 deletions checks/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,38 @@ func TestParseVariablesLeavesMissingValuesUnset(t *testing.T) {
}
}

func TestParseVariablesCapturesBodyRegex(t *testing.T) {
variables := map[string]string{}
err := parseVariables(
[]byte(`<a href="/password-reset/abc123">reset</a>`),
[]api.HTTPRequestResponseVariable{
{Name: "resetToken", BodyRegex: `/password-reset/([a-z0-9]+)`},
},
variables,
)
if err != nil {
t.Fatalf("unexpected parseVariables error: %v", err)
}
if variables["resetToken"] != "abc123" {
t.Fatalf("resetToken = %q, want abc123", variables["resetToken"])
}
}

func TestParseVariablesRequiresCaptureSource(t *testing.T) {
variables := map[string]string{}
err := parseVariables(
[]byte(`{"token":"abc123"}`),
[]api.HTTPRequestResponseVariable{{Name: "token"}},
variables,
)
if err == nil {
t.Fatal("expected parseVariables error")
}
if err.Error() != "invalid response variable configuration" {
t.Fatalf("error = %q, want invalid response variable configuration", err.Error())
}
}

func TestParseHeaderVariablesLeavesMissingValuesUnset(t *testing.T) {
variables := map[string]string{}
err := parseHeaderVariables(
Expand Down
5 changes: 3 additions & 2 deletions client/lessons.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ type HTTPBasicAuth struct {
}

type HTTPRequestResponseVariable struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Name string `yaml:"name"`
Path string `yaml:"path"`
BodyRegex string `yaml:"bodyRegex"`
}

type HTTPRequestResponseHeaderVariable struct {
Expand Down
17 changes: 14 additions & 3 deletions render/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,12 @@ func savedVariablesForHTTPResult(result api.HTTPRequestResult) []variableEntry {
if value == "" {
continue
}

description := responseVariableDescription(responseVariable)
entries = append(entries, variableEntry{
name: responseVariable.Name,
value: value,
description: "JSON Body " + responseVariable.Path,
description: description,
})
}
for _, responseHeaderVariable := range result.Request.ResponseHeaderVariables {
Expand All @@ -72,9 +74,11 @@ func missingSaveVariablesForHTTPResult(result api.HTTPRequestResult) []variableE
if result.Variables[responseVariable.Name] != "" {
continue
}

description := responseVariableDescription(responseVariable)
entries = append(entries, variableEntry{
name: responseVariable.Name,
description: "JSON Body " + responseVariable.Path,
description: description,
})
}
for _, responseHeaderVariable := range result.Request.ResponseHeaderVariables {
Expand All @@ -93,7 +97,7 @@ func responseHeaderVariableDescription(v api.HTTPRequestResponseHeaderVariable)
if v.Regex == "" {
return "Response Header " + v.Header
}
return fmt.Sprintf("Response Header %s matching %s", v.Header, v.Regex)
return "Response Header " + v.Header + " pattern"
}

func availableVariablesForHTTPResult(result api.HTTPRequestResult) (entries []variableEntry, expectsVariables bool) {
Expand Down Expand Up @@ -205,3 +209,10 @@ func availableVariablesForCLIResult(result api.CLICommandResult) (entries []vari

return entries, expectsVariables
}

func responseVariableDescription(v api.HTTPRequestResponseVariable) string {
if v.BodyRegex != "" {
return "Response Body pattern"
}
return "JSON Body " + v.Path
}
Comment thread
theodore-s-beers marked this conversation as resolved.
15 changes: 10 additions & 5 deletions render/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ import (
func TestHTTPVariableSections(t *testing.T) {
result := api.HTTPRequestResult{
Variables: map[string]string{
"authToken": "token-123",
"shortCode": "abc123",
"sessionID": "session-123",
"authToken": "token-123",
"resetToken": "reset-123",
"shortCode": "abc123",
"sessionID": "session-123",
},
Request: api.CLIStepHTTPRequest{
ResponseVariables: []api.HTTPRequestResponseVariable{
{Name: "shortCode", Path: ".short_code"},
{Name: "resetToken", BodyRegex: `/password-reset/([a-z0-9]+)`},
{Name: "missingCode", Path: ".missing_code"},
{Name: "missingResetToken", BodyRegex: `/missing/([a-z0-9]+)`},
},
ResponseHeaderVariables: []api.HTTPRequestResponseHeaderVariable{
{Name: "sessionID", Header: "Set-Cookie", Regex: "session_id=([^;]+)"},
Expand All @@ -42,11 +45,13 @@ func TestHTTPVariableSections(t *testing.T) {

wantContains := []string{
"Variables Saved:",
"sessionID: session-123 (Response Header Set-Cookie matching session_id=([^;]+))",
"resetToken: reset-123 (Response Body pattern)",
"sessionID: session-123 (Response Header Set-Cookie pattern)",
"shortCode: abc123 (JSON Body .short_code)",
"Variables Missing:",
"missingCode: [not found] (JSON Body .missing_code)",
"missingSessionID: [not found] (Response Header Set-Cookie matching missing=([^;]+))",
"missingResetToken: [not found] (Response Body pattern)",
"missingSessionID: [not found] (Response Header Set-Cookie pattern)",
"Variables Available:",
"authToken: token-123 (Request Header \"Authorization\")",
"shortCode: abc123 (Request URL)",
Expand Down