diff --git a/tide/capture_test.go b/tide/capture_test.go index 2fc761e..29ca171 100644 --- a/tide/capture_test.go +++ b/tide/capture_test.go @@ -272,6 +272,53 @@ func post(t *testing.T, url, session, ct, body string) { } } +func TestReplayExpandsUnquotedIDPlaceholdersAfterCapture(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/login": + _, _ = w.Write([]byte(`{"token":"` + testJWT + `","user":{"id":1}}`)) + case "/genres": + _, _ = w.Write([]byte(`{"data":[{"id":1,"name":"Rock","album_count":0}]}`)) + default: + http.NotFound(w, r) + } + })) + t.Cleanup(srv.Close) + + flow := Flow{ + Version: 1, + Name: "seed-then-list", + Steps: []Step{ + { + ID: "login", + Request: Request{Method: http.MethodPost, Path: "/login"}, + Response: Response{ + Status: 200, + Headers: jsonCT(), + Body: Body(`{"token":"{{jwt:alice}}","user":{"id":{{id:alice}}}}`), + }, + Capture: []CaptureRule{ + {From: "response.json", Path: "$.token", As: "jwt:alice", Category: "jwt"}, + {From: "response.json", Path: "$.user.id", As: "id:alice"}, + }, + }, + { + ID: "genres", + Request: Request{Method: http.MethodGet, Path: "/genres"}, + Response: Response{ + Status: 200, + Headers: jsonCT(), + Body: Body(`{"data":[{"id":{{id:alice}},"name":"Rock","album_count":0}]}`), + }, + }, + }, + } + if _, err := ReplayFlow(context.Background(), flow, ReplayConfig{Target: srv.URL}); err != nil { + t.Fatalf("replay with unquoted id placeholders: %v", err) + } +} + func TestScrubShortNumericIDsDoNotCorruptPaths(t *testing.T) { store, err := OpenStore("") if err != nil { diff --git a/tide/replay.go b/tide/replay.go index 5ef8524..65238ec 100644 --- a/tide/replay.go +++ b/tide/replay.go @@ -59,7 +59,18 @@ func ReplayFlow(ctx context.Context, flow Flow, cfg ReplayConfig) (Result, error skipRest = true continue } - _ = ScrubStep(store, &live) + if err := store.Save(); err != nil { + return result, fmt.Errorf("tide: replay step %s: %w", step.ID, err) + } + want.Response, err = expandResponse(want.Response, store) + if err != nil { + sr.OK = false + result.OK = false + sr.Diffs = append(sr.Diffs, Diff{Path: "expected", Expected: "resolved placeholders", Actual: err.Error()}) + result.Steps = append(result.Steps, sr) + skipRest = true + continue + } sr.Diffs = append(sr.Diffs, compareStep(want, live.Response)...) if len(sr.Diffs) > 0 { sr.OK = false diff --git a/tide/variables.go b/tide/variables.go index 4ad93fa..1084f39 100644 --- a/tide/variables.go +++ b/tide/variables.go @@ -199,6 +199,26 @@ func expandRequest(req Request, store *Store) (Request, error) { return out, nil } +func expandResponse(resp Response, store *Store) (Response, error) { + out := resp + var err error + if resp.Headers != nil { + out.Headers = make(map[string]string, len(resp.Headers)) + for k, v := range resp.Headers { + out.Headers[k], err = store.Expand(v) + if err != nil { + return Response{}, err + } + } + } + body, err := store.Expand(string(resp.Body)) + if err != nil { + return Response{}, err + } + out.Body = Body(body) + return out, nil +} + // CaptureStep writes named values from the step into the store. func CaptureStep(store *Store, step *Step) error { if store == nil || step == nil || len(step.Capture) == 0 {