package httpapi import ( "encoding/json" "strings" "testing" ) // TestFlexBoolUnmarshal covers the exact production failure: the LLM emitted // `"privileged":0` / `"nesting":1` (numbers) and the strict bool field made the // already-approved pct_create execution fail to parse, so the LXC was never // created. func TestFlexBoolUnmarshal(t *testing.T) { type cfg struct { Privileged flexBool `json:"privileged"` Nesting flexBool `json:"nesting"` } cases := []struct { in string privileged bool nesting bool wantErr bool }{ {`{"privileged":0,"nesting":1}`, false, true, false}, // the prod payload {`{"privileged":false,"nesting":true}`, false, true, false}, // canonical {`{"privileged":"1","nesting":"0"}`, true, false, false}, // stringified {`{"privileged":"true","nesting":"no"}`, true, false, false}, {`{}`, false, false, false}, // absent → zero {`{"privileged":"maybe"}`, false, false, true}, } for _, c := range cases { var out cfg err := json.Unmarshal([]byte(c.in), &out) if (err != nil) != c.wantErr { t.Fatalf("%s: err=%v wantErr=%v", c.in, err, c.wantErr) } if c.wantErr { continue } if bool(out.Privileged) != c.privileged || bool(out.Nesting) != c.nesting { t.Errorf("%s: got priv=%v nest=%v want priv=%v nest=%v", c.in, bool(out.Privileged), bool(out.Nesting), c.privileged, c.nesting) } } } func TestResolveTemplate(t *testing.T) { avail := []string{ "debian-12-standard_12.7-1_amd64.tar.zst", "debian-13-standard_13.0-1_amd64.tar.zst", "ubuntu-24.04-standard_24.04-2_amd64.tar.zst", } cases := []struct { requested string want string }{ {"debian-13-standard_13.0-1_amd64.tar.zst", "debian-13-standard_13.0-1_amd64.tar.zst"}, // exact {"debian-13", "debian-13-standard_13.0-1_amd64.tar.zst"}, // prefix {"", "debian-13-standard_13.0-1_amd64.tar.zst"}, // auto newest debian {"debian-99", "debian-13-standard_13.0-1_amd64.tar.zst"}, // miss prefix → auto debian } for _, c := range cases { if got := resolveTemplate(c.requested, avail); got != c.want { t.Errorf("resolveTemplate(%q): got %q want %q", c.requested, got, c.want) } } if got := resolveTemplate("debian-13", nil); got != "" { t.Errorf("empty cache should yield empty, got %q", got) } } // TestJSONErrValidForNastyOutput guards the bug where command output with // quotes/backslashes/newlines produced invalid JSON, failing the ::jsonb cast // and silently dropping the execution's final status update. func TestJSONErrValidForNastyOutput(t *testing.T) { nasty := "CT 132 already exists on node \"hubris\"\n\tpath C:\\x\r\n\x00 100%" for _, payload := range [][]byte{ jsonErr("%s", nasty), jsonErr("list templates on %s: %s", "host:strong", nasty), } { var m map[string]any if err := json.Unmarshal(payload, &m); err != nil { t.Fatalf("jsonErr produced invalid JSON: %v\npayload=%s", err, payload) } if _, ok := m["error"]; !ok { t.Errorf("missing error key: %s", payload) } } } // TestGatewayPreflightPassed guards the exact bug found live: "UNREACHABLE" // contains "REACHABLE" as a substring, so a strings.Contains(out,"REACHABLE") // check is true for BOTH outcomes and can never fail. Exact-match only. func TestGatewayPreflightPassed(t *testing.T) { cases := []struct { out string want bool }{ {"PREFLIGHT_OK", true}, {"PREFLIGHT_OK\n", true}, {" PREFLIGHT_OK ", true}, {"PREFLIGHT_FAIL", false}, {"PREFLIGHT_FAIL\n", false}, {"", false}, {"some garbage output", false}, // the specific historical bug: a naive substring check on the old // REACHABLE/UNREACHABLE markers would have called this true. {"UNREACHABLE", false}, } for _, c := range cases { if got := gatewayPreflightPassed(c.out); got != c.want { t.Errorf("gatewayPreflightPassed(%q) = %v, want %v", c.out, got, c.want) } } } func TestProvisionScript(t *testing.T) { s := provisionScript([]string{"docker.io", "git"}, "echo hi > /root/x") // Network/DNS gate must come before apt. gate := strings.Index(s, "getent hosts") apt := strings.Index(s, "apt-get update") post := strings.Index(s, "echo hi > /root/x") if gate < 0 || apt < 0 || post < 0 { t.Fatalf("missing sections: gate=%d apt=%d post=%d\n%s", gate, apt, post, s) } if !(gate < apt && apt < post) { t.Errorf("wrong ordering: gate=%d apt=%d post=%d", gate, apt, post) } if !strings.Contains(s, "nameserver 1.1.1.1") { t.Error("missing DNS self-heal fallback") } if !strings.Contains(s, "docker.io git") { t.Error("packages not joined into install line") } // No packages: no apt lines, but post_install and gate still present. s2 := provisionScript(nil, "systemctl status foo") if strings.Contains(s2, "apt-get install") { t.Error("apt install should be absent when no packages requested") } if !strings.Contains(s2, "systemctl status foo") || !strings.Contains(s2, "getent hosts") { t.Error("post_install or gate missing in no-package case") } } func TestSanitizePkgs(t *testing.T) { in := []string{"docker.io", "git", "rm -rf /", "curl;wget", "python3-pip", ""} got := sanitizePkgs(in) want := map[string]bool{"docker.io": true, "git": true, "python3-pip": true} if len(got) != len(want) { t.Fatalf("got %v want keys %v", got, want) } for _, g := range got { if !want[g] { t.Errorf("unexpected package survived sanitize: %q", g) } } }