Files
summercms/tide/roundtrip_test.go
Jakub Zych f6e1b892bc feat(02-01): record and replay one route through the summer CLI
- Add a generic tide flow schema with YAML fixture IO and HTTP record/replay
- Register parity:record and parity:replay on the bonfire summer tool
- Diff JSON scalars at $.path and non-JSON bodies at the changed byte offset

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 12:20:22 +02:00

119 lines
3.3 KiB
Go

package tide
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestParityRoundTrip(t *testing.T) {
ctx := context.Background()
specPath := filepath.Join("testdata", "one-route-spec.yaml")
spec, err := LoadFlow(specPath)
if err != nil {
t.Fatal(err)
}
t.Run("json record replay and scalar mismatch", func(t *testing.T) {
orig := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/sample" || r.Method != http.MethodGet {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"data":"ok"}`))
}))
t.Cleanup(orig.Close)
changed := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"data":"no"}`))
}))
t.Cleanup(changed.Close)
recorded, err := RecordFlow(ctx, spec, RecordConfig{Target: orig.URL})
if err != nil {
t.Fatal(err)
}
out := filepath.Join(t.TempDir(), "sample.yaml")
if err := SaveFlow(out, recorded); err != nil {
t.Fatal(err)
}
raw, err := os.ReadFile(out)
if err != nil {
t.Fatal(err)
}
text := string(raw)
if !strings.Contains(text, "version: 1") {
t.Fatalf("missing version:\n%s", text)
}
if !strings.Contains(text, "body: |") && !strings.Contains(text, "body: |-") {
t.Fatalf("body is not a literal block scalar:\n%s", text)
}
if !strings.Contains(text, `{"data":"ok"}`) {
t.Fatalf("missing recorded JSON body:\n%s", text)
}
if !strings.Contains(text, "application/json") {
t.Fatalf("missing Content-Type:\n%s", text)
}
loaded, err := LoadFlow(out)
if err != nil {
t.Fatal(err)
}
if _, err := ReplayFlow(ctx, loaded, ReplayConfig{Target: orig.URL}); err != nil {
t.Fatalf("identical backend must replay: %v", err)
}
_, err = ReplayFlow(ctx, loaded, ReplayConfig{Target: changed.URL})
if err == nil {
t.Fatal("changed JSON must fail")
}
msg := err.Error()
if !strings.Contains(msg, "$.data") {
t.Fatalf("mismatch missing $.data: %s", msg)
}
if !strings.Contains(msg, "ok") || !strings.Contains(msg, "no") {
t.Fatalf("mismatch missing expected/actual: %s", msg)
}
})
t.Run("non-json byte offset mismatch", func(t *testing.T) {
orig := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("hello"))
}))
t.Cleanup(orig.Close)
changed := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("hallo"))
}))
t.Cleanup(changed.Close)
recorded, err := RecordFlow(ctx, spec, RecordConfig{Target: orig.URL})
if err != nil {
t.Fatal(err)
}
if _, err := ReplayFlow(ctx, recorded, ReplayConfig{Target: orig.URL}); err != nil {
t.Fatalf("identical plain body must replay: %v", err)
}
_, err = ReplayFlow(ctx, recorded, ReplayConfig{Target: changed.URL})
if err == nil {
t.Fatal("changed bytes must fail")
}
msg := err.Error()
if !strings.Contains(msg, "1") {
t.Fatalf("byte mismatch missing offset: %s", msg)
}
})
}