e2e: the bridge's smoke test (internal/e2e/selfserve, tag e2e)
TestBridgeBoxesReportThemselves builds host.E2E beside a console over one store, runs one pass and one status tick and finds each box's status block naming its own box id, instance id and lane, the source's naming the instance's facts, the serial consumed and the pass and status fault points fired once per box.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 file changed,
+107
−0
internal/e2e/selfserve/bridge_test.go+107 −0
diff --git a/internal/e2e/selfserve/bridge_test.go b/internal/e2e/selfserve/bridge_test.go| new file mode 100644 |
| index 000000000..30ac54e26 |
| --- /dev/null |
| +++ b/internal/e2e/selfserve/bridge_test.go |
| @@ -0,0 +1,107 @@ |
| +//go:build e2e |
| + |
| +// Package selfserve holds the self-serve region moves' end-to-end |
| +// witnesses: the console's doors and self-serve pass over host.E2E, the two |
| +// real boxes of internal/host's offline move tests, in one process over one |
| +// deployment store. Every file here carries the e2e build tag; the gate runs |
| +// them with `go test -tags e2e ./internal/e2e/...`. |
| +package selfserve |
| + |
| +import ( |
| + "context" |
| + "path/filepath" |
| + "slices" |
| + "testing" |
| + |
| + "github.com/carlosframework/platform/internal/console" |
| + "github.com/carlosframework/platform/internal/host" |
| + "github.com/carlosframework/platform/internal/release" |
| + "github.com/carlosframework/platform/internal/status" |
| +) |
| + |
| +// TestBridgeBoxesReportThemselves is the bridge's smoke test: built over the |
| +// store a console uses, with that console beside it, one pass and one |
| +// status tick on both boxes leave each box's status block naming its own |
| +// box id, instance id and lane, and the source's naming the instance's host |
| +// facts; the serial is consumed; and the pass and status fault points fire |
| +// once per box. |
| +func TestBridgeBoxesReportThemselves(t *testing.T) { |
| + ctx := context.Background() |
| + store := release.NewDirStore(t.TempDir()) |
| + e := host.NewE2E(t, store) |
| + |
| + dir := t.TempDir() |
| + cst, err := console.Open(filepath.Join(dir, "console.db")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Cleanup(func() { cst.Close() }) |
| + if _, err := console.NewServer(console.Config{Origin: "http://127.0.0.1", Keymail: "http://keymail.invalid", |
| + Registry: filepath.Join(dir, "registry.db"), Releases: store, ReleasePub: e.ReleasePub(), Key: e.ReleaseKey(), AppScheme: "http", |
| + HomeRegion: host.E2EHomeRegion, SecretsPub: e.SecretsPub()}, cst); err != nil { |
| + t.Fatal(err) |
| + } |
| + |
| + fired := map[string]int{} |
| + for _, label := range []string{host.E2ESourceBox, host.E2ETargetBox} { |
| + for _, point := range []string{host.FaultPass, host.FaultStatus} { |
| + p := point + label |
| + e.Fault(p, func() { fired[p]++ }) |
| + } |
| + } |
| + if err := e.Pass(ctx); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := e.Status(ctx); err != nil { |
| + t.Fatal(err) |
| + } |
| + for p, n := range fired { |
| + if n != 1 { |
| + t.Errorf("fault point %s fired %d times, want once", p, n) |
| + } |
| + } |
| + if len(fired) != 4 { |
| + t.Errorf("fault points fired: %v, want the pass and status points of both boxes", fired) |
| + } |
| + if err := release.BumpSerial(ctx, store, e.Clock().Now()); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := e.Serial(ctx); err != nil { |
| + t.Fatal(err) |
| + } |
| + |
| + boxes, complete, err := status.LoadBoxesComplete(ctx, store) |
| + if err != nil || !complete { |
| + t.Fatalf("status boxes: complete %v, %v", complete, err) |
| + } |
| + byLabel := map[string]*status.BoxMetrics{} |
| + for _, b := range boxes { |
| + byLabel[b.Label] = b.Metrics |
| + } |
| + for label, lane := range map[string]string{host.E2ESourceBox: "data-uk", host.E2ETargetBox: "data-ie"} { |
| + h := e.Box(label) |
| + m := byLabel[label] |
| + if m == nil { |
| + t.Fatalf("no status block for %s (have %v)", label, boxes) |
| + } |
| + if h.BoxID() == "" || h.InstanceID() == "" { |
| + t.Fatalf("%s: box id %q, instance id %q", label, h.BoxID(), h.InstanceID()) |
| + } |
| + if m.BoxID != h.BoxID() || m.InstanceID != h.InstanceID() { |
| + t.Errorf("%s's block names box %q instance %q, want %q and %q", label, m.BoxID, m.InstanceID, h.BoxID(), h.InstanceID()) |
| + } |
| + if !slices.Equal(m.Lanes, []string{lane}) { |
| + t.Errorf("%s's lanes %v, want [%s]", label, m.Lanes, lane) |
| + } |
| + } |
| + src := byLabel[host.E2ESourceBox] |
| + if f, ok := src.Hosts[host.E2EHost]; !ok || f.Incarnation == 0 && f.FilesEntries == 0 { |
| + t.Errorf("the source's block has no facts for %s: %+v", host.E2EHost, src.Hosts) |
| + } |
| + if _, ok := byLabel[host.E2ETargetBox].Hosts[host.E2EHost]; ok { |
| + t.Errorf("the target reports facts for %s before any move", host.E2EHost) |
| + } |
| + if e.Box(host.E2ESourceBox).DataBucket() != "data-ie" || e.Box(host.E2ETargetBox).DataBucket() != "data-uk" { |
| + t.Errorf("data buckets %q and %q", e.Box(host.E2ESourceBox).DataBucket(), e.Box(host.E2ETargetBox).DataBucket()) |
| + } |
| +} |