| 1 | package status |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | "time" |
| 9 | |
| 10 | "github.com/carlosframework/platform/internal/registry" |
| 11 | "github.com/carlosframework/platform/internal/release" |
| 12 | ) |
| 13 | |
| 14 | // TestBoxMetricsCarryThePlatformSelfUpdateState proves Task 5's three new |
| 15 | // Probes fields (Platform, SystemChannel, System) reach Box.Metrics, reach |
| 16 | // the wire under their json names, count toward the "something is known" |
| 17 | // emptiness guard on their own, and — like every other Metrics field — never |
| 18 | // reach the public fleet feed. |
| 19 | func TestBoxMetricsCarryThePlatformSelfUpdateState(t *testing.T) { |
| 20 | at := time.Date(2026, 9, 6, 9, 0, 0, 0, time.UTC) |
| 21 | p := Probes{Release: "v1", Platform: "linux-arm64", SystemChannel: "canary", |
| 22 | System: &SystemState{Channel: "canary", Version: "v2", PromotedAt: at, Outcome: "rolled-back", Reason: "gate", At: at}} |
| 23 | b := CollectBox(nil, p, at) |
| 24 | if b.Metrics == nil || b.Metrics.Platform != "linux-arm64" || b.Metrics.SystemChannel != "canary" || b.Metrics.System == nil || b.Metrics.System.Outcome != "rolled-back" { |
| 25 | t.Fatalf("metrics = %+v", b.Metrics) |
| 26 | } |
| 27 | out, _ := json.Marshal(b) |
| 28 | for _, want := range []string{`"platform":"linux-arm64"`, `"system_channel":"canary"`, `"outcome":"rolled-back"`} { |
| 29 | if !strings.Contains(string(out), want) { |
| 30 | t.Errorf("json lacks %s: %s", want, out) |
| 31 | } |
| 32 | } |
| 33 | // A box with only the new fields is not "empty". |
| 34 | b2 := CollectBox(nil, Probes{Platform: "linux-arm64"}, at) |
| 35 | if b2.Metrics == nil { |
| 36 | t.Fatal("platform alone must produce a metrics block") |
| 37 | } |
| 38 | // The public fleet feed still never carries the metrics block. |
| 39 | s := release.NewDirStore(t.TempDir()) |
| 40 | if err := WriteBox(context.Background(), s, b); err != nil { |
| 41 | t.Fatal(err) |
| 42 | } |
| 43 | f, err := BuildFleet(context.Background(), s, nil, at, 5*time.Minute) |
| 44 | if err != nil { |
| 45 | t.Fatal(err) |
| 46 | } |
| 47 | fb, _ := json.Marshal(f) |
| 48 | if strings.Contains(string(fb), "system_channel") || strings.Contains(string(fb), "rolled-back") { |
| 49 | t.Fatalf("fleet.json leaked the private metrics: %s", fb) |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | // TestCollectBoxCarriesTheLayoutReport pins Task 5's Probes.Layout: it |
| 54 | // round-trips through CollectBox into Box.Metrics.Layout and onto the wire |
| 55 | // under its json names, and a Probes with none produces no "layout" key at |
| 56 | // all — the same "unknown stays absent, never a guess" rule every other |
| 57 | // Metrics field follows. |
| 58 | func TestCollectBoxCarriesTheLayoutReport(t *testing.T) { |
| 59 | at := time.Date(2026, 9, 25, 9, 0, 0, 0, time.UTC) |
| 60 | report := &LayoutReport{ |
| 61 | Mode: "nested", |
| 62 | Rows: map[string]RowLayout{ |
| 63 | "h.example.com": {Layout: "nested"}, |
| 64 | "f.example.com": {Layout: "foreign", Reason: `db "x" socket "y" are neither the flat nor the nested defaults`}, |
| 65 | "q.example.com": {Layout: "nested", Note: "migrated at 2026-10-06T00:00:00Z: kept aside /data/layout/aside/q.example.com/creds.q-1-ab"}, |
| 66 | }, |
| 67 | } |
| 68 | b := CollectBox(nil, Probes{Layout: report}, at) |
| 69 | if b.Metrics == nil || b.Metrics.Layout == nil { |
| 70 | t.Fatalf("metrics.layout missing: %+v", b.Metrics) |
| 71 | } |
| 72 | if b.Metrics.Layout.Mode != "nested" { |
| 73 | t.Errorf("mode = %q, want nested", b.Metrics.Layout.Mode) |
| 74 | } |
| 75 | if row := b.Metrics.Layout.Rows["h.example.com"]; row.Layout != "nested" || row.Reason != "" { |
| 76 | t.Errorf("h.example.com row = %+v", row) |
| 77 | } |
| 78 | if row := b.Metrics.Layout.Rows["f.example.com"]; row.Layout != "foreign" || row.Reason == "" { |
| 79 | t.Errorf("f.example.com row = %+v", row) |
| 80 | } |
| 81 | out, err := json.Marshal(b) |
| 82 | if err != nil { |
| 83 | t.Fatal(err) |
| 84 | } |
| 85 | for _, want := range []string{`"layout":{"mode":"nested"`, `"h.example.com":{"layout":"nested"}`, `"f.example.com":{"layout":"foreign","reason":`, `"q.example.com":{"layout":"nested","note":"migrated at`} { |
| 86 | if !strings.Contains(string(out), want) { |
| 87 | t.Errorf("json lacks %s: %s", want, out) |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | // A Probes with no Layout at all publishes no "layout" key — not the |
| 92 | // field present-and-null, absent entirely, same as every other Metrics |
| 93 | // field with nothing to report. |
| 94 | b2 := CollectBox(nil, Probes{Release: "v1"}, at) |
| 95 | out2, err := json.Marshal(b2) |
| 96 | if err != nil { |
| 97 | t.Fatal(err) |
| 98 | } |
| 99 | if strings.Contains(string(out2), `"layout"`) { |
| 100 | t.Fatalf("layout key present with no Layout probe: %s", out2) |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | // TestBoxMetricsCarryAlertDelivery pins discussion #192's visibility ask: a |
| 105 | // box says whether its own alert rules have anywhere to deliver, an |
| 106 | // all-off answer is published (it is the answer that matters), the field |
| 107 | // alone makes a metrics block, and the public fleet feed never sees it. |
| 108 | func TestBoxMetricsCarryAlertDelivery(t *testing.T) { |
| 109 | at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC) |
| 110 | |
| 111 | none := CollectBox(nil, Probes{Alerts: &AlertDelivery{}}, at) |
| 112 | if none.Metrics == nil || none.Metrics.Alerts == nil || none.Metrics.Alerts.Delivered() { |
| 113 | t.Fatalf("an all-off answer must publish as not delivered: %+v", none.Metrics) |
| 114 | } |
| 115 | out, _ := json.Marshal(none) |
| 116 | if !strings.Contains(string(out), `"alerts":{"webhook":false,"sns":false}`) { |
| 117 | t.Errorf("json lacks the explicit all-off alerts block: %s", out) |
| 118 | } |
| 119 | |
| 120 | unknown := CollectBox(nil, Probes{Release: "v1"}, at) |
| 121 | if unknown.Metrics == nil || unknown.Metrics.Alerts != nil { |
| 122 | t.Errorf("an unasked box must publish no alerts key: %+v", unknown.Metrics) |
| 123 | } |
| 124 | |
| 125 | sns := CollectBox(nil, Probes{Alerts: &AlertDelivery{SNS: true, SNSFailures: 2}}, at) |
| 126 | out, _ = json.Marshal(sns) |
| 127 | if !strings.Contains(string(out), `"sns":true`) || !strings.Contains(string(out), `"sns_failures":2`) { |
| 128 | t.Errorf("json lacks the sns sink or its failures: %s", out) |
| 129 | } |
| 130 | |
| 131 | s := release.NewDirStore(t.TempDir()) |
| 132 | if err := WriteBox(context.Background(), s, none); err != nil { |
| 133 | t.Fatal(err) |
| 134 | } |
| 135 | f, err := BuildFleet(context.Background(), s, nil, at, 5*time.Minute) |
| 136 | if err != nil { |
| 137 | t.Fatal(err) |
| 138 | } |
| 139 | fb, _ := json.Marshal(f) |
| 140 | if strings.Contains(string(fb), "alerts") || strings.Contains(string(fb), "webhook") { |
| 141 | t.Fatalf("fleet.json leaked the alert-sink state: %s", fb) |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | func TestCollectBoxCarriesGeoIP(t *testing.T) { |
| 146 | box := CollectBox(nil, Probes{GeoIP: &GeoIPState{Mode: "mirror", SHA256: "abc", Type: "DBIP-City-Lite"}}, time.Now()) |
| 147 | if box.Metrics == nil || box.Metrics.GeoIP == nil || box.Metrics.GeoIP.SHA256 != "abc" { |
| 148 | t.Fatalf("metrics = %+v", box.Metrics) |
| 149 | } |
| 150 | raw, err := json.Marshal(box) |
| 151 | if err != nil { |
| 152 | t.Fatal(err) |
| 153 | } |
| 154 | if !strings.Contains(string(raw), `"geoip":{"mode":"mirror"`) { |
| 155 | t.Fatalf("json = %s", raw) |
| 156 | } |
| 157 | if b := CollectBox(nil, Probes{}, time.Now()); b.Metrics != nil { |
| 158 | t.Fatal("no GeoIP and nothing else known must still mean no metrics block") |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | func TestTheRolloutReportRidesThePrivateMetrics(t *testing.T) { |
| 163 | r := &RolloutReport{Hosts: map[string]HostRollout{"a.example.com": {App: "blog", Installed: "v2", Serving: "v1", Attempt: AttemptFailed, HeldBy: "v2", Reason: ReasonHealth}}} |
| 164 | box := CollectBox(nil, Probes{Rollout: r}, time.Now()) |
| 165 | if box.Metrics == nil || box.Metrics.Rollout == nil || box.Metrics.Rollout.Hosts["a.example.com"].HeldBy != "v2" { |
| 166 | t.Fatalf("metrics = %+v", box.Metrics) |
| 167 | } |
| 168 | if b := CollectBox(nil, Probes{}, time.Now()); b.Metrics != nil { |
| 169 | t.Fatal("the flag off publishes no rollout block and no Metrics because of it") |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | // RouteServing is the build the agent started for each route (plan 5's |
| 174 | // ServingVersion), carried beside RouteVersions, root's install stamp. |
| 175 | func TestCollectBoxCarriesTheServingBuild(t *testing.T) { |
| 176 | routes := []registry.Route{ |
| 177 | {Host: "side.titogo.bab.sidecar.internal", Kind: "sidecar", Version: "v2", ServingVersion: "v1"}, |
| 178 | {Host: "never.titogo.bab.sidecar.internal", Kind: "sidecar", Version: "v2"}, |
| 179 | {Host: "alias.example.com", Platform: true, ServingVersion: "v9"}, |
| 180 | } |
| 181 | b := CollectBox(routes, Probes{MachineID: "m"}, time.Now()) |
| 182 | if b.Metrics == nil || b.Metrics.RouteServing["side.titogo.bab.sidecar.internal"] != "v1" { |
| 183 | t.Fatalf("RouteServing = %+v", b.Metrics) |
| 184 | } |
| 185 | if _, ok := b.Metrics.RouteServing["never.titogo.bab.sidecar.internal"]; ok { |
| 186 | t.Error("a route that never started has a serving build") |
| 187 | } |
| 188 | if _, ok := b.Metrics.RouteServing["alias.example.com"]; ok { |
| 189 | t.Error("a platform alias carries a serving build") |
| 190 | } |
| 191 | } |
| 192 | |