| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "log" |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | "time" |
| 11 | ) |
| 12 | |
| 13 | func TestSystemChannelDefaultsToStable(t *testing.T) { |
| 14 | env := func(v string) func(string) string { |
| 15 | return func(k string) string { |
| 16 | if k == systemChannelEnv { |
| 17 | return v |
| 18 | } |
| 19 | return "" |
| 20 | } |
| 21 | } |
| 22 | for _, c := range []struct { |
| 23 | name string |
| 24 | set string |
| 25 | want, wantBadCh string |
| 26 | }{ |
| 27 | {"unset", "", "stable", ""}, |
| 28 | {"set", " canary ", "canary", ""}, |
| 29 | {"a branch canary", "canary/paul", "canary/paul", ""}, |
| 30 | // Not a channel: it would travel into a bucket key and an on-box |
| 31 | // state file, so the box follows stable and says which value it |
| 32 | // refused, rather than asking for a pointer nobody publishes. |
| 33 | {"a path traversal", "../../etc", "stable", "../../etc"}, |
| 34 | {"a word with a space", "not a channel", "stable", "not a channel"}, |
| 35 | } { |
| 36 | t.Run(c.name, func(t *testing.T) { |
| 37 | got, bad := systemChannel(env(c.set)) |
| 38 | if got != c.want || bad != c.wantBadCh { |
| 39 | t.Fatalf("systemChannel(%q) = %q, %q; want %q, %q", c.set, got, bad, c.want, c.wantBadCh) |
| 40 | } |
| 41 | }) |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | // The refused value is said ONCE, where the leg is armed — not on every one |
| 46 | // of the two-second ticks that read the channel. |
| 47 | func TestNewSystemLegSaysWhichChannelValueItRefused(t *testing.T) { |
| 48 | var buf bytes.Buffer |
| 49 | out, flags := log.Writer(), log.Flags() |
| 50 | log.SetOutput(&buf) |
| 51 | log.SetFlags(0) |
| 52 | defer func() { log.SetOutput(out); log.SetFlags(flags) }() |
| 53 | |
| 54 | leg := newSystemLeg(nil, nil, func(k string) string { |
| 55 | if k == systemChannelEnv { |
| 56 | return "canary/too/deep" |
| 57 | } |
| 58 | return "" |
| 59 | }, t.TempDir(), t.TempDir()) |
| 60 | if leg.channel != "stable" { |
| 61 | t.Fatalf("channel = %q, want stable", leg.channel) |
| 62 | } |
| 63 | said := strings.Count(buf.String(), "canary/too/deep") |
| 64 | lines := len(strings.Split(strings.TrimSpace(buf.String()), "\n")) |
| 65 | if said != 1 || lines != 1 { |
| 66 | t.Fatalf("arming said the refused value %d times over %d lines:\n%s", said, lines, buf.String()) |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | func TestSystemStateProbeReadsRootsResult(t *testing.T) { |
| 71 | opt := t.TempDir() |
| 72 | if got := systemStateProbe(opt); got != nil { |
| 73 | t.Fatalf("no result.json → %+v, want nil", got) |
| 74 | } |
| 75 | root := systemRootDir(opt) |
| 76 | os.MkdirAll(root, 0o755) |
| 77 | at := time.Date(2026, 9, 6, 9, 0, 0, 0, time.UTC) |
| 78 | writeJSONDurable(filepath.Join(root, "result.json"), systemResult{Channel: "stable", Version: "v3", PromotedAt: at, Outcome: "applied", At: at}, 0o644) |
| 79 | got := systemStateProbe(opt) |
| 80 | if got == nil || got.Version != "v3" || got.Outcome != "applied" || !got.PromotedAt.Equal(at) { |
| 81 | t.Fatalf("probe = %+v", got) |
| 82 | } |
| 83 | } |
| 84 | |