From 4451c2f39fc3dd811eaf92fe6027dd577d115c90 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Thu, 17 Sep 2026 12:46:38 +0200 Subject: [PATCH] fix(02-03): resolve relative seed specs and skip only recorded seeds Manifest seed paths are fixtures-relative. Skip re-hitting the backend only when every seed step already has a recorded status so a hand-written spec can be recorded in place. Co-authored-by: Cursor --- tide/manifest.go | 58 +++++++++++++++++++++++++++++++++++-------- tide/manifest_test.go | 41 ++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/tide/manifest.go b/tide/manifest.go index a673f1f..bcfc75c 100644 --- a/tide/manifest.go +++ b/tide/manifest.go @@ -315,14 +315,6 @@ func RecordManifest(ctx context.Context, m Manifest, cfg ManifestConfig) (Covera } func recordSeed(ctx context.Context, seed *Seed, cfg ManifestConfig) error { - spec, err := LoadFlow(seed.Spec) - if err != nil { - return err - } - flow, err := RecordFlow(ctx, spec, RecordConfig{Target: cfg.Target, Store: cfg.Store, Rules: cfg.Rules}) - if err != nil { - return err - } dest := seed.Fixture if dest == "" { dest = seed.Spec @@ -330,12 +322,58 @@ func recordSeed(ctx context.Context, seed *Seed, cfg ManifestConfig) error { if cfg.Fixtures != "" && !filepath.IsAbs(dest) { dest = filepath.Join(cfg.Fixtures, dest) } - if _, err := os.Stat(dest); err == nil && !cfg.Update { - return nil + if !cfg.Update { + if recorded, err := seedAlreadyRecorded(dest); err != nil { + return err + } else if recorded { + return nil + } + } + spec, err := LoadFlow(resolveSeedSpec(seed.Spec, cfg.Fixtures)) + if err != nil { + return err + } + flow, err := RecordFlow(ctx, spec, RecordConfig{Target: cfg.Target, Store: cfg.Store, Rules: cfg.Rules}) + if err != nil { + return err } return SaveFlow(dest, flow) } +func seedAlreadyRecorded(path string) (bool, error) { + st, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + if st.IsDir() || st.Size() == 0 { + return false, nil + } + flow, err := LoadFlow(path) + if err != nil { + return false, nil + } + for _, step := range flow.Steps { + if step.Response.Status == 0 { + return false, nil + } + } + return len(flow.Steps) > 0, nil +} + +func resolveSeedSpec(spec, fixtures string) string { + if spec == "" || filepath.IsAbs(spec) || fixtures == "" { + return spec + } + joined := filepath.Join(fixtures, spec) + if _, err := os.Stat(joined); err == nil { + return joined + } + return spec +} + func recordRoute(ctx context.Context, route Route, cfg ManifestConfig) error { for _, c := range route.Cases { dest := caseFixturePath(cfg.Fixtures, route, c) diff --git a/tide/manifest_test.go b/tide/manifest_test.go index 6ed0fa6..7202f1e 100644 --- a/tide/manifest_test.go +++ b/tide/manifest_test.go @@ -365,3 +365,44 @@ func TestCoveragePendingMismatchDoesNotFail(t *testing.T) { } } +func TestRecordSeedResolvesRelativeSpec(t *testing.T) { + fixtures := t.TempDir() + if err := os.MkdirAll(filepath.Join(fixtures, "seed"), 0o755); err != nil { + t.Fatal(err) + } + spec := Flow{Version: 1, Name: "seed", Steps: []Step{{ + ID: "ok", + Request: Request{Method: http.MethodGet, Path: "/seed"}, + }}} + if err := SaveFlow(filepath.Join(fixtures, "seed", "bootstrap.yaml"), spec); err != nil { + t.Fatal(err) + } + hits := 0 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + hits++ + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"ok":true}`)) + })) + t.Cleanup(srv.Close) + m := Manifest{ + Version: 1, + AuthGroups: []string{"public"}, + Seed: &Seed{Spec: "seed/bootstrap.yaml", Fixture: "seed/bootstrap.yaml"}, + Routes: []Route{{ + ID: "GET /later public", Method: http.MethodGet, Path: "/later", + AuthGroup: "public", Status: StatusPending, + }}, + } + if _, err := RecordManifest(context.Background(), m, ManifestConfig{Target: srv.URL, Fixtures: fixtures}); err != nil { + t.Fatal(err) + } + if hits != 1 { + t.Fatalf("seed hits %d", hits) + } + if _, err := RecordManifest(context.Background(), m, ManifestConfig{Target: srv.URL, Fixtures: fixtures}); err != nil { + t.Fatal(err) + } + if hits != 1 { + t.Fatalf("existing seed must not re-hit PHP: %d", hits) + } +}