Skip to content

Commit 56d624b

Browse files
make http response var capture more generic
1 parent e02ffef commit 56d624b

5 files changed

Lines changed: 89 additions & 16 deletions

File tree

checks/http.go

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -199,16 +199,40 @@ func truncateAndStringifyBody(body []byte) string {
199199
}
200200

201201
func parseVariables(body []byte, vardefs []api.HTTPRequestResponseVariable, variables map[string]string) error {
202+
bodyString := string(body)
203+
202204
for _, vardef := range vardefs {
203-
vals, err := valsFromJqPath(vardef.Path, string(body))
204-
if err != nil {
205-
return err
206-
}
207-
if len(vals) != 1 || vals[0] == nil {
208-
continue
205+
switch {
206+
case vardef.Path != "" && vardef.BodyRegex != "":
207+
return fmt.Errorf("invalid response variable configuration")
208+
209+
case vardef.BodyRegex != "":
210+
re, err := regexp.Compile(vardef.BodyRegex)
211+
if err != nil {
212+
return fmt.Errorf("invalid response body variable configuration")
213+
}
214+
if re.NumSubexp() != 1 {
215+
return fmt.Errorf("invalid response body variable configuration")
216+
}
217+
matches := re.FindStringSubmatch(bodyString)
218+
if len(matches) == 2 && matches[1] != "" {
219+
variables[vardef.Name] = matches[1]
220+
}
221+
222+
case vardef.Path != "":
223+
vals, err := valsFromJqPath(vardef.Path, bodyString)
224+
if err != nil {
225+
return err
226+
}
227+
if len(vals) == 1 && vals[0] != nil {
228+
variables[vardef.Name] = fmt.Sprintf("%v", vals[0])
229+
}
230+
231+
default:
232+
return fmt.Errorf("invalid response variable configuration")
209233
}
210-
variables[vardef.Name] = fmt.Sprintf("%v", vals[0])
211234
}
235+
212236
return nil
213237
}
214238

@@ -223,10 +247,10 @@ func parseHeaderVariables(headers map[string]string, vardefs []api.HTTPRequestRe
223247
if vardef.Regex != "" {
224248
re, err := regexp.Compile(vardef.Regex)
225249
if err != nil {
226-
return err
250+
return fmt.Errorf("invalid response header variable configuration")
227251
}
228252
if re.NumSubexp() != 1 {
229-
return fmt.Errorf("regex for header variable %q must have exactly one capture group", vardef.Name)
253+
return fmt.Errorf("invalid response header variable configuration")
230254
}
231255

232256
matches := re.FindStringSubmatch(headerValue)

checks/http_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,38 @@ func TestParseVariablesLeavesMissingValuesUnset(t *testing.T) {
202202
}
203203
}
204204

205+
func TestParseVariablesCapturesBodyRegex(t *testing.T) {
206+
variables := map[string]string{}
207+
err := parseVariables(
208+
[]byte(`<a href="/password-reset/abc123">reset</a>`),
209+
[]api.HTTPRequestResponseVariable{
210+
{Name: "resetToken", BodyRegex: `/password-reset/([a-z0-9]+)`},
211+
},
212+
variables,
213+
)
214+
if err != nil {
215+
t.Fatalf("unexpected parseVariables error: %v", err)
216+
}
217+
if variables["resetToken"] != "abc123" {
218+
t.Fatalf("resetToken = %q, want abc123", variables["resetToken"])
219+
}
220+
}
221+
222+
func TestParseVariablesRequiresCaptureSource(t *testing.T) {
223+
variables := map[string]string{}
224+
err := parseVariables(
225+
[]byte(`{"token":"abc123"}`),
226+
[]api.HTTPRequestResponseVariable{{Name: "token"}},
227+
variables,
228+
)
229+
if err == nil {
230+
t.Fatal("expected parseVariables error")
231+
}
232+
if err.Error() != "invalid response variable configuration" {
233+
t.Fatalf("error = %q, want invalid response variable configuration", err.Error())
234+
}
235+
}
236+
205237
func TestParseHeaderVariablesLeavesMissingValuesUnset(t *testing.T) {
206238
variables := map[string]string{}
207239
err := parseHeaderVariables(

client/lessons.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,9 @@ type HTTPBasicAuth struct {
110110
}
111111

112112
type HTTPRequestResponseVariable struct {
113-
Name string `yaml:"name"`
114-
Path string `yaml:"path"`
113+
Name string `yaml:"name"`
114+
Path string `yaml:"path"`
115+
BodyRegex string `yaml:"bodyRegex"`
115116
}
116117

117118
type HTTPRequestResponseHeaderVariable struct {

render/variables.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ func savedVariablesForHTTPResult(result api.HTTPRequestResult) []variableEntry {
4646
if value == "" {
4747
continue
4848
}
49+
50+
description := responseVariableDescription(responseVariable)
4951
entries = append(entries, variableEntry{
5052
name: responseVariable.Name,
5153
value: value,
52-
description: "JSON Body " + responseVariable.Path,
54+
description: description,
5355
})
5456
}
5557
for _, responseHeaderVariable := range result.Request.ResponseHeaderVariables {
@@ -72,9 +74,11 @@ func missingSaveVariablesForHTTPResult(result api.HTTPRequestResult) []variableE
7274
if result.Variables[responseVariable.Name] != "" {
7375
continue
7476
}
77+
78+
description := responseVariableDescription(responseVariable)
7579
entries = append(entries, variableEntry{
7680
name: responseVariable.Name,
77-
description: "JSON Body " + responseVariable.Path,
81+
description: description,
7882
})
7983
}
8084
for _, responseHeaderVariable := range result.Request.ResponseHeaderVariables {
@@ -205,3 +209,10 @@ func availableVariablesForCLIResult(result api.CLICommandResult) (entries []vari
205209

206210
return entries, expectsVariables
207211
}
212+
213+
func responseVariableDescription(v api.HTTPRequestResponseVariable) string {
214+
if v.BodyRegex != "" {
215+
return "Response Body pattern"
216+
}
217+
return "JSON Body " + v.Path
218+
}

render/variables_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ import (
1010
func TestHTTPVariableSections(t *testing.T) {
1111
result := api.HTTPRequestResult{
1212
Variables: map[string]string{
13-
"authToken": "token-123",
14-
"shortCode": "abc123",
15-
"sessionID": "session-123",
13+
"authToken": "token-123",
14+
"resetToken": "reset-123",
15+
"shortCode": "abc123",
16+
"sessionID": "session-123",
1617
},
1718
Request: api.CLIStepHTTPRequest{
1819
ResponseVariables: []api.HTTPRequestResponseVariable{
1920
{Name: "shortCode", Path: ".short_code"},
21+
{Name: "resetToken", BodyRegex: `/password-reset/([a-z0-9]+)`},
2022
{Name: "missingCode", Path: ".missing_code"},
23+
{Name: "missingResetToken", BodyRegex: `/missing/([a-z0-9]+)`},
2124
},
2225
ResponseHeaderVariables: []api.HTTPRequestResponseHeaderVariable{
2326
{Name: "sessionID", Header: "Set-Cookie", Regex: "session_id=([^;]+)"},
@@ -42,10 +45,12 @@ func TestHTTPVariableSections(t *testing.T) {
4245

4346
wantContains := []string{
4447
"Variables Saved:",
48+
"resetToken: reset-123 (Response Body pattern)",
4549
"sessionID: session-123 (Response Header Set-Cookie matching session_id=([^;]+))",
4650
"shortCode: abc123 (JSON Body .short_code)",
4751
"Variables Missing:",
4852
"missingCode: [not found] (JSON Body .missing_code)",
53+
"missingResetToken: [not found] (Response Body pattern)",
4954
"missingSessionID: [not found] (Response Header Set-Cookie matching missing=([^;]+))",
5055
"Variables Available:",
5156
"authToken: token-123 (Request Header \"Authorization\")",

0 commit comments

Comments
 (0)