package httpapi import ( "encoding/json" "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) } } } // provisionScript and sanitizePkgs were removed when pct_create was made // atomic (create + start + register only) — installing packages and running // setup scripts is now the agent's own job via follow-up `run` calls, which // already has its own classifier/sanitization tests in internal/policy.