Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

agents-ui.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

agents-ui.test.tsBlame143 lines · 1 contributor
ddb25a6Claude1/**
2 * Block K8 — Agent inbox + controls UI tests.
3 *
4 * Pure form-parser tests cover the allowlist + coercion logic.
5 * Route smokes only assert auth behaviour — DB-backed flows live in
6 * the integration suite.
7 */
8
9import { describe, it, expect } from "bun:test";
10import app from "../app";
11import {
12 parseAgentSettingsForm,
13 type AgentSettingsInput,
14} from "../routes/agents";
15
16describe("agents UI — parseAgentSettingsForm", () => {
17 it("accepts a valid full form", () => {
18 const parsed = parseAgentSettingsForm({
19 enabled_kinds: ["triage", "fix"],
20 daily_budget_cents: "500",
21 monthly_budget_cents: "10000",
22 max_runs_per_hour: "30",
23 paused: "on",
24 });
25 expect(parsed.enabledKinds).toEqual(["triage", "fix"]);
26 expect(parsed.dailyBudgetCents).toBe(500);
27 expect(parsed.monthlyBudgetCents).toBe(10000);
28 expect(parsed.maxRunsPerHour).toBe(30);
29 expect(parsed.paused).toBe(true);
30 });
31
32 it("filters out unknown kinds", () => {
33 const parsed = parseAgentSettingsForm({
34 enabled_kinds: ["triage", "hacking", "DROP TABLE"],
35 });
36 expect(parsed.enabledKinds).toEqual(["triage"]);
37 });
38
39 it("uses defaults when form is empty", () => {
40 const parsed = parseAgentSettingsForm({} as AgentSettingsInput);
41 expect(parsed.enabledKinds).toEqual([]);
42 expect(parsed.dailyBudgetCents).toBe(100);
43 expect(parsed.monthlyBudgetCents).toBe(2000);
44 expect(parsed.maxRunsPerHour).toBe(20);
45 expect(parsed.paused).toBe(false);
46 });
47
48 it("rejects negative budgets (falls back to default)", () => {
49 const parsed = parseAgentSettingsForm({
50 daily_budget_cents: "-50",
51 monthly_budget_cents: "-1000",
52 });
53 expect(parsed.dailyBudgetCents).toBe(100);
54 expect(parsed.monthlyBudgetCents).toBe(2000);
55 });
56
57 it("caps over-large budgets", () => {
58 const parsed = parseAgentSettingsForm({
59 daily_budget_cents: "99999999",
60 monthly_budget_cents: "99999999999",
61 max_runs_per_hour: "99999",
62 });
63 expect(parsed.dailyBudgetCents).toBe(1_000_000);
64 expect(parsed.monthlyBudgetCents).toBe(50_000_000);
65 expect(parsed.maxRunsPerHour).toBe(1000);
66 });
67
68 it("accepts a single kind as a string (form single-select edge)", () => {
69 const parsed = parseAgentSettingsForm({ enabled_kinds: "heal_bot" });
70 expect(parsed.enabledKinds).toEqual(["heal_bot"]);
71 });
72
73 it("handles non-numeric budget gracefully", () => {
74 const parsed = parseAgentSettingsForm({
75 daily_budget_cents: "abc",
76 max_runs_per_hour: "xyz",
77 });
78 expect(parsed.dailyBudgetCents).toBe(100);
79 expect(parsed.maxRunsPerHour).toBe(20);
80 });
81
82 it("treats paused=true as true, paused=other as false", () => {
83 expect(parseAgentSettingsForm({ paused: "true" }).paused).toBe(true);
84 expect(parseAgentSettingsForm({ paused: "on" }).paused).toBe(true);
85 expect(parseAgentSettingsForm({ paused: "off" }).paused).toBe(false);
86 expect(parseAgentSettingsForm({ paused: "1" }).paused).toBe(false);
87 });
88});
89
90describe("agents UI — route auth smokes", () => {
91 it("POST /:owner/:repo/agents/:id/kill without session → 302 /login", async () => {
92 const res = await app.fetch(
93 new Request("http://test/alice/repo/agents/00000000-0000-0000-0000-000000000000/kill", {
94 method: "POST",
95 })
96 );
97 expect(res.status).toBe(302);
98 expect(res.headers.get("location")).toMatch(/\/login/);
99 });
100
101 it("GET /:owner/:repo/settings/agents without session → 302 /login", async () => {
102 const res = await app.fetch(
103 new Request("http://test/alice/repo/settings/agents")
104 );
105 expect(res.status).toBe(302);
106 expect(res.headers.get("location")).toMatch(/\/login/);
107 });
108
109 it("POST /:owner/:repo/settings/agents without session → 302 /login", async () => {
110 const res = await app.fetch(
111 new Request("http://test/alice/repo/settings/agents", {
112 method: "POST",
113 body: new URLSearchParams({ daily_budget_cents: "100" }),
114 })
115 );
116 expect(res.status).toBe(302);
117 expect(res.headers.get("location")).toMatch(/\/login/);
118 });
119
120 it("POST /:owner/:repo/settings/agents/pause without session → 302 /login", async () => {
121 const res = await app.fetch(
122 new Request("http://test/alice/repo/settings/agents/pause", {
123 method: "POST",
124 })
125 );
126 expect(res.status).toBe(302);
127 expect(res.headers.get("location")).toMatch(/\/login/);
128 });
129
130 it("GET /admin/agents without session → 302 /login", async () => {
131 const res = await app.fetch(new Request("http://test/admin/agents"));
132 expect(res.status).toBe(302);
133 expect(res.headers.get("location")).toMatch(/\/login/);
134 });
135
136 it("POST /admin/agents/pause-all without session → 302 /login", async () => {
137 const res = await app.fetch(
138 new Request("http://test/admin/agents/pause-all", { method: "POST" })
139 );
140 expect(res.status).toBe(302);
141 expect(res.headers.get("location")).toMatch(/\/login/);
142 });
143});