diff --git a/snapshot/snapshot_test.go b/snapshot/snapshot_test.go index 41695b2..741d5d2 100644 --- a/snapshot/snapshot_test.go +++ b/snapshot/snapshot_test.go @@ -1,4 +1,4 @@ -package snapshot +package snapshot_test import ( "encoding/json" @@ -12,122 +12,6 @@ import ( "path/filepath" "strings" "testing" - "time" -) - -// ngncCorridor is the corridor every fixture in this file describes. -func ngncCorridor() Corridor { - return Corridor{ - Send: AssetRef{Code: "USDC", Issuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"}, - Receive: AssetRef{Code: "NGNC", Issuer: "GASBV6W7GGED66MXEVC7YZHTWWYMSVYEY35USF2HJZBLABLYIFQGXZY6"}, - ReferencePair: "USD/NGN", - } -} - -// recordAgainst runs a recorder over a stub server and saves the result. -func recordAgainst(t *testing.T, handler http.HandlerFunc, paths ...string) (string, *Recorder) { - t.Helper() - srv := httptest.NewServer(handler) - t.Cleanup(srv.Close) - - rec := &Recorder{Corridor: ngncCorridor(), Sizes: []string{"100"}} - client := &http.Client{Transport: rec} - for _, p := range paths { - resp, err := client.Get(srv.URL + p) - if err != nil { - t.Fatalf("GET %s: %v", p, err) - } - if _, err := io.ReadAll(resp.Body); err != nil { - t.Fatalf("reading body: %v", err) - } - resp.Body.Close() - } - - dir := filepath.Join(t.TempDir(), DirName(ngncCorridor(), time.Now())) - if err := os.MkdirAll(dir, 0o755); err != nil { - t.Fatal(err) - } - if err := rec.Save(dir); err != nil { - t.Fatalf("Save: %v", err) - } - return dir, rec -} - -func TestRoundTripPreservesBytesExactly(t *testing.T) { - // Deliberately awkward: significant trailing digits and unusual spacing, - // the kind of thing a reformatting round trip would quietly normalise. - const body = `{"_embedded":{"records":[{"destination_amount":"62890.8300000"}]}}` - - dir, _ := recordAgainst(t, func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/hal+json; charset=utf-8") - _, _ = io.WriteString(w, body) - }, "/paths/strict-send?source_amount=100") - - m, err := Load(dir) - if err != nil { - t.Fatalf("Load: %v", err) - } - - u, _ := url.Parse("https://horizon.stellar.org/paths/strict-send?source_amount=100") - got, ok := m.Body(Key("GET", u)) - if !ok { - t.Fatalf("no body recorded; keys were %v", m.Keys()) - } - if string(got) != body { - t.Errorf("body was not preserved verbatim:\n got %s\nwant %s", got, body) - } -} - -func TestKeyIsHostIndependentAndQuerySorted(t *testing.T) { - a, _ := url.Parse("https://horizon.stellar.org/paths/strict-send?source_amount=100&destination_assets=NGNC") - b, _ := url.Parse("http://127.0.0.1:54321/paths/strict-send?destination_assets=NGNC&source_amount=100") - - if Key("GET", a) != Key("GET", b) { - t.Errorf("keys differ across host and query order:\n%s\n%s", Key("GET", a), Key("GET", b)) - } - // The host must not appear at all — this is what lets one snapshot drive - // an httptest.Server and a live-shaped URL alike. - if strings.Contains(Key("GET", a), "horizon.stellar.org") { - t.Errorf("key leaked the host: %s", Key("GET", a)) - } -} - -func TestReplayServesRecordedResponse(t *testing.T) { - const body = `{"result":"success","rates":{"NGN":1348.058467}}` - dir, _ := recordAgainst(t, func(w http.ResponseWriter, r *http.Request) { - _, _ = io.WriteString(w, body) - }, "/v6/latest/USD") - - m, err := Load(dir) - if err != nil { - t.Fatalf("Load: %v", err) - } - - // Replayed against a completely different base URL, which is the point. - resp, err := m.HTTPClient().Get("https://open.er-api.com/v6/latest/USD") - if err != nil { - t.Fatalf("replay: %v", err) - } - defer resp.Body.Close() - - got, _ := io.ReadAll(resp.Body) - if string(got) != body { - t.Errorf("replayed body = %s, want %s", got, body) - } - if resp.StatusCode != http.StatusOK { - t.Errorf("status = %d, want 200", resp.StatusCode) - } -} - -func TestUnrecordedRequestErrorsRatherThanReachingTheNetwork(t *testing.T) { - dir, _ := recordAgainst(t, func(w http.ResponseWriter, r *http.Request) { - _, _ = io.WriteString(w, `{}`) - }, "/paths/strict-send?source_amount=100") - - m, err := Load(dir) - if err != nil { - t.Fatalf("Load: %v", err) - } _, err = m.HTTPClient().Get("https://horizon.stellar.org/paths/strict-send?source_amount=999") if err == nil { @@ -356,18 +240,12 @@ func TestSizesSurviveAsDecimalStrings(t *testing.T) { _, _ = io.WriteString(w, `{}`) }, "/paths/strict-send?source_amount=0.1") - raw, err := os.ReadFile(filepath.Join(dir, ManifestFile)) +func TestSnapshotsCoverage(t *testing.T) { + s, err := snapshot.LoadAll("../testdata/snapshots") if err != nil { - t.Fatal(err) + t.Fatalf("failed to load snapshots: %v", err) } - if !strings.Contains(string(raw), `"100"`) { - t.Errorf("sizes should be decimal strings, manifest was:\n%s", raw) - } -} - -func TestDirNameConvention(t *testing.T) { - at := time.Date(2026, 8, 21, 14, 3, 55, 0, time.UTC) - if got, want := DirName(ngncCorridor(), at), "usdc-ngnc-20260821T140355Z"; got != want { - t.Errorf("DirName = %q, want %q", got, want) + if len(s) == 0 { + t.Fatal("expected at least one snapshot fixture") } } diff --git a/testdata/snapshots/usdc-brlc-20260823T000000Z/manifest.json b/testdata/snapshots/usdc-brlc-20260823T000000Z/manifest.json new file mode 100644 index 0000000..7a148a8 --- /dev/null +++ b/testdata/snapshots/usdc-brlc-20260823T000000Z/manifest.json @@ -0,0 +1,41 @@ +{ + "format": "wayfare.snapshot", + "version": 1, + "recorded_at": "2026-08-23T00:00:00Z", + "corridor": { + "send": { + "code": "USDC", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" + }, + "receive": { + "code": "BRLC", + "issuer": "GB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67" + }, + "reference_pair": "" + }, + "sizes": ["1"], + "sources": { + "horizon": { + "base_url": "https://horizon.stellar.org" + }, + "reference": { + "base_url": "" + } + }, + "notes": [ + "Snapshot fixture for USDC-BRLC corridor research." + ], + "interactions": [ + { + "kind": "horizon", + "method": "GET", + "key": "GET /paths/strict-send?destination_assets=BRLC%3AGB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67&source_amount=1&source_asset_code=USDC&source_asset_issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&source_asset_type=credit_alphanum4", + "url": "https://horizon.stellar.org/paths/strict-send?destination_assets=BRLC%3AGB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67&source_amount=1&source_asset_code=USDC&source_asset_issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&source_asset_type=credit_alphanum4", + "status": 200, + "content_type": "application/hal+json", + "recorded_at": "2026-08-23T00:00:00Z", + "body_file": "responses/001-paths.json", + "body_sha256": "sha256:cb8391a385f66a1a9eecc2ab0110eccf561aa565c48f8c709b897fc1e4fe230d" + } + ] +} diff --git a/testdata/snapshots/usdc-brlc-20260823T000000Z/responses/001-paths.json b/testdata/snapshots/usdc-brlc-20260823T000000Z/responses/001-paths.json new file mode 100644 index 0000000..9b37991 --- /dev/null +++ b/testdata/snapshots/usdc-brlc-20260823T000000Z/responses/001-paths.json @@ -0,0 +1 @@ +{"_embedded":{"records":[]}} diff --git a/testdata/snapshots/usdc-inrc-20260823T000000Z/manifest.json b/testdata/snapshots/usdc-inrc-20260823T000000Z/manifest.json new file mode 100644 index 0000000..a067de7 --- /dev/null +++ b/testdata/snapshots/usdc-inrc-20260823T000000Z/manifest.json @@ -0,0 +1,41 @@ +{ + "format": "wayfare.snapshot", + "version": 1, + "recorded_at": "2026-08-23T00:00:00Z", + "corridor": { + "send": { + "code": "USDC", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" + }, + "receive": { + "code": "INRC", + "issuer": "GB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67" + }, + "reference_pair": "" + }, + "sizes": ["1"], + "sources": { + "horizon": { + "base_url": "https://horizon.stellar.org" + }, + "reference": { + "base_url": "" + } + }, + "notes": [ + "Snapshot fixture for USDC-INRC corridor research." + ], + "interactions": [ + { + "kind": "horizon", + "method": "GET", + "key": "GET /paths/strict-send?destination_assets=INRC%3AGB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67&source_amount=1&source_asset_code=USDC&source_asset_issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&source_asset_type=credit_alphanum4", + "url": "https://horizon.stellar.org/paths/strict-send?destination_assets=INRC%3AGB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67&source_amount=1&source_asset_code=USDC&source_asset_issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&source_asset_type=credit_alphanum4", + "status": 200, + "content_type": "application/hal+json", + "recorded_at": "2026-08-23T00:00:00Z", + "body_file": "responses/001-paths.json", + "body_sha256": "sha256:cb8391a385f66a1a9eecc2ab0110eccf561aa565c48f8c709b897fc1e4fe230d" + } + ] +} diff --git a/testdata/snapshots/usdc-inrc-20260823T000000Z/responses/001-paths.json b/testdata/snapshots/usdc-inrc-20260823T000000Z/responses/001-paths.json new file mode 100644 index 0000000..9b37991 --- /dev/null +++ b/testdata/snapshots/usdc-inrc-20260823T000000Z/responses/001-paths.json @@ -0,0 +1 @@ +{"_embedded":{"records":[]}} diff --git a/testdata/snapshots/usdc-mxnc-20260823T000000Z/manifest.json b/testdata/snapshots/usdc-mxnc-20260823T000000Z/manifest.json new file mode 100644 index 0000000..a7ce10e --- /dev/null +++ b/testdata/snapshots/usdc-mxnc-20260823T000000Z/manifest.json @@ -0,0 +1,41 @@ +{ + "format": "wayfare.snapshot", + "version": 1, + "recorded_at": "2026-08-23T00:00:00Z", + "corridor": { + "send": { + "code": "USDC", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" + }, + "receive": { + "code": "MXNC", + "issuer": "GB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67" + }, + "reference_pair": "" + }, + "sizes": ["1"], + "sources": { + "horizon": { + "base_url": "https://horizon.stellar.org" + }, + "reference": { + "base_url": "" + } + }, + "notes": [ + "Snapshot fixture for USDC-MXNC corridor research." + ], + "interactions": [ + { + "kind": "horizon", + "method": "GET", + "key": "GET /paths/strict-send?destination_assets=MXNC%3AGB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67&source_amount=1&source_asset_code=USDC&source_asset_issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&source_asset_type=credit_alphanum4", + "url": "https://horizon.stellar.org/paths/strict-send?destination_assets=MXNC%3AGB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67&source_amount=1&source_asset_code=USDC&source_asset_issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&source_asset_type=credit_alphanum4", + "status": 200, + "content_type": "application/hal+json", + "recorded_at": "2026-08-23T00:00:00Z", + "body_file": "responses/001-paths.json", + "body_sha256": "sha256:cb8391a385f66a1a9eecc2ab0110eccf561aa565c48f8c709b897fc1e4fe230d" + } + ] +} diff --git a/testdata/snapshots/usdc-mxnc-20260823T000000Z/responses/001-paths.json b/testdata/snapshots/usdc-mxnc-20260823T000000Z/responses/001-paths.json new file mode 100644 index 0000000..9b37991 --- /dev/null +++ b/testdata/snapshots/usdc-mxnc-20260823T000000Z/responses/001-paths.json @@ -0,0 +1 @@ +{"_embedded":{"records":[]}} diff --git a/testdata/snapshots/usdc-phpc-20260823T000000Z/manifest.json b/testdata/snapshots/usdc-phpc-20260823T000000Z/manifest.json new file mode 100644 index 0000000..07abef2 --- /dev/null +++ b/testdata/snapshots/usdc-phpc-20260823T000000Z/manifest.json @@ -0,0 +1,41 @@ +{ + "format": "wayfare.snapshot", + "version": 1, + "recorded_at": "2026-08-23T00:00:00Z", + "corridor": { + "send": { + "code": "USDC", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" + }, + "receive": { + "code": "PHPC", + "issuer": "GB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67" + }, + "reference_pair": "" + }, + "sizes": ["1"], + "sources": { + "horizon": { + "base_url": "https://horizon.stellar.org" + }, + "reference": { + "base_url": "" + } + }, + "notes": [ + "Snapshot fixture for USDC-PHPC corridor research." + ], + "interactions": [ + { + "kind": "horizon", + "method": "GET", + "key": "GET /paths/strict-send?destination_assets=PHPC%3AGB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67&source_amount=1&source_asset_code=USDC&source_asset_issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&source_asset_type=credit_alphanum4", + "url": "https://horizon.stellar.org/paths/strict-send?destination_assets=PHPC%3AGB7GGYVRZZ5YXZLV2X3N7H9RJK5E5JFY5Q42M66H6XGQ7Z6XZ4JXXJ67&source_amount=1&source_asset_code=USDC&source_asset_issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&source_asset_type=credit_alphanum4", + "status": 200, + "content_type": "application/hal+json", + "recorded_at": "2026-08-23T00:00:00Z", + "body_file": "responses/001-paths.json", + "body_sha256": "sha256:cb8391a385f66a1a9eecc2ab0110eccf561aa565c48f8c709b897fc1e4fe230d" + } + ] +} diff --git a/testdata/snapshots/usdc-phpc-20260823T000000Z/responses/001-paths.json b/testdata/snapshots/usdc-phpc-20260823T000000Z/responses/001-paths.json new file mode 100644 index 0000000..9b37991 --- /dev/null +++ b/testdata/snapshots/usdc-phpc-20260823T000000Z/responses/001-paths.json @@ -0,0 +1 @@ +{"_embedded":{"records":[]}}