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

gatetest-client.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.

gatetest-client.test.tsBlame234 lines · 1 contributor
a6d8fd5Claude1/**
2 * Block K — Gatetest client tests.
3 *
4 * Exercises the env-driven configuration toggle, the auth header builder,
5 * and the offline short-circuits. When a key is present, we mock
6 * globalThis.fetch with a 500 to confirm graceful degradation to the
7 * offline result shape.
8 */
9
10import { describe, it, expect, beforeEach, afterEach } from "bun:test";
11import {
12 buildAuthHeaders,
13 healSuite,
14 isConfigured,
15 runAndRepair,
16 stackTraceToTest,
17} from "../lib/gatetest-client";
18
19const ENV_KEYS = ["GATETEST_API_KEY", "GATETEST_BASE_URL"] as const;
20
21let savedEnv: Record<string, string | undefined> = {};
22let savedFetch: typeof fetch;
23
24beforeEach(() => {
25 savedEnv = {};
26 for (const k of ENV_KEYS) savedEnv[k] = process.env[k];
27 // Start every test with a clean slate.
28 for (const k of ENV_KEYS) delete process.env[k];
29 savedFetch = globalThis.fetch;
30});
31
32afterEach(() => {
33 for (const k of ENV_KEYS) {
34 const v = savedEnv[k];
35 if (v === undefined) delete process.env[k];
36 else process.env[k] = v;
37 }
38 globalThis.fetch = savedFetch;
39});
40
41// ---------------------------------------------------------------------------
42// isConfigured
43// ---------------------------------------------------------------------------
44
45describe("gatetest-client — isConfigured", () => {
46 it("returns false when no API key is set", () => {
47 expect(isConfigured()).toBe(false);
48 });
49
50 it("returns true once GATETEST_API_KEY is set", () => {
51 process.env.GATETEST_API_KEY = "sk-test-abc";
52 expect(isConfigured()).toBe(true);
53 });
54
55 it("flips back to false when the key is removed mid-process", () => {
56 process.env.GATETEST_API_KEY = "sk-test-abc";
57 expect(isConfigured()).toBe(true);
58 delete process.env.GATETEST_API_KEY;
59 expect(isConfigured()).toBe(false);
60 });
61});
62
63// ---------------------------------------------------------------------------
64// buildAuthHeaders
65// ---------------------------------------------------------------------------
66
67describe("gatetest-client — buildAuthHeaders", () => {
68 it("returns only Content-Type when no key is present", () => {
69 const h = buildAuthHeaders();
70 expect(h["Content-Type"]).toBe("application/json");
71 expect(h["Authorization"]).toBeUndefined();
72 });
73
74 it("includes a Bearer token when the key is present", () => {
75 process.env.GATETEST_API_KEY = "sk-live-xyz";
76 const h = buildAuthHeaders();
77 expect(h["Content-Type"]).toBe("application/json");
78 expect(h["Authorization"]).toBe("Bearer sk-live-xyz");
79 });
80});
81
82// ---------------------------------------------------------------------------
83// runAndRepair
84// ---------------------------------------------------------------------------
85
86describe("gatetest-client — runAndRepair", () => {
87 it("returns offline result with zeros when no API key is set", async () => {
88 // No fetch mock — the method MUST short-circuit.
89 globalThis.fetch = (() => {
90 throw new Error("fetch must not be called in offline mode");
91 }) as unknown as typeof fetch;
92 const result = await runAndRepair({ repo: "o/r", ref: "main" });
93 expect(result.offline).toBe(true);
94 expect(result.passed).toBe(false);
95 expect(result.totalTests).toBe(0);
96 expect(result.failedBefore).toBe(0);
97 expect(result.failedAfter).toBe(0);
98 expect(result.repairs).toEqual([]);
99 expect(result.unfixable).toEqual([]);
100 expect(result.durationMs).toBe(0);
101 });
102
103 it("falls back to offline on a 500 response", async () => {
104 process.env.GATETEST_API_KEY = "sk-test";
105 globalThis.fetch = (async () =>
106 new Response("boom", { status: 500 })) as unknown as typeof fetch;
107 const result = await runAndRepair({ repo: "o/r", ref: "main" });
108 expect(result.offline).toBe(true);
109 expect(result.passed).toBe(false);
110 expect(result.totalTests).toBe(0);
111 });
112
113 it("parses a healthy 200 response into a structured result", async () => {
114 process.env.GATETEST_API_KEY = "sk-test";
115 const payload = {
116 passed: true,
117 totalTests: 42,
118 failedBefore: 3,
119 failedAfter: 0,
120 repairs: [
121 { file: "a.ts", before: "x", after: "y", reason: "flake" },
122 ],
123 unfixable: [],
124 durationMs: 12345,
125 };
126 globalThis.fetch = (async () =>
127 new Response(JSON.stringify(payload), {
128 status: 200,
129 headers: { "Content-Type": "application/json" },
130 })) as unknown as typeof fetch;
131 const result = await runAndRepair({
132 repo: "o/r",
133 ref: "main",
134 targetGlob: "src/**",
135 });
136 expect(result.offline).toBe(false);
137 expect(result.passed).toBe(true);
138 expect(result.totalTests).toBe(42);
139 expect(result.repairs).toHaveLength(1);
140 });
141});
142
143// ---------------------------------------------------------------------------
144// stackTraceToTest
145// ---------------------------------------------------------------------------
146
147describe("gatetest-client — stackTraceToTest", () => {
148 it("returns a deterministic offline stub when no key is set", async () => {
149 const result = await stackTraceToTest({
150 repo: "o/r",
151 stackTrace: "TypeError: cannot read 'x' of undefined\n at foo",
152 language: "typescript",
153 });
154 expect(result.offline).toBe(true);
155 expect(result.framework).toBe("fallback");
156 expect(result.testCode).toContain("TODO");
157 expect(result.suggestedPath.endsWith(".test.ts")).toBe(true);
158 });
159
160 it("picks a pytest path for python offline stubs", async () => {
161 const result = await stackTraceToTest({
162 repo: "o/r",
163 stackTrace: "AttributeError: foo",
164 language: "python",
165 });
166 expect(result.offline).toBe(true);
167 expect(result.suggestedPath.endsWith(".py")).toBe(true);
168 expect(result.testCode).toContain("def test_");
169 });
170
171 it("falls back to offline on a 500 response", async () => {
172 process.env.GATETEST_API_KEY = "sk-test";
173 globalThis.fetch = (async () =>
174 new Response("down", { status: 500 })) as unknown as typeof fetch;
175 const result = await stackTraceToTest({
176 repo: "o/r",
177 stackTrace: "boom",
178 });
179 expect(result.offline).toBe(true);
180 expect(result.framework).toBe("fallback");
181 });
182});
183
184// ---------------------------------------------------------------------------
185// healSuite
186// ---------------------------------------------------------------------------
187
188describe("gatetest-client — healSuite", () => {
189 it("returns offline zeros when no key is set", async () => {
190 const result = await healSuite({ repo: "o/r" });
191 expect(result.offline).toBe(true);
192 expect(result.flakyFound).toBe(0);
193 expect(result.deadFound).toBe(0);
194 expect(result.coverageGapsFound).toBe(0);
195 expect(result.prDraftBranch).toBeNull();
196 });
197
198 it("falls back to offline on a 500 response", async () => {
199 process.env.GATETEST_API_KEY = "sk-test";
200 globalThis.fetch = (async () =>
201 new Response("fail", { status: 500 })) as unknown as typeof fetch;
202 const result = await healSuite({ repo: "o/r" });
203 expect(result.offline).toBe(true);
204 expect(result.flakyFound).toBe(0);
205 });
206
207 it("falls back to offline when fetch itself throws (network down)", async () => {
208 process.env.GATETEST_API_KEY = "sk-test";
209 globalThis.fetch = (async () => {
210 throw new Error("ECONNREFUSED");
211 }) as unknown as typeof fetch;
212 const result = await healSuite({ repo: "o/r" });
213 expect(result.offline).toBe(true);
214 expect(result.prDraftBranch).toBeNull();
215 });
216
217 it("parses a healthy 200 response", async () => {
218 process.env.GATETEST_API_KEY = "sk-test";
219 globalThis.fetch = (async () =>
220 new Response(
221 JSON.stringify({
222 flakyFound: 2,
223 deadFound: 1,
224 coverageGapsFound: 4,
225 prDraftBranch: "gatetest/heal-42",
226 }),
227 { status: 200, headers: { "Content-Type": "application/json" } }
228 )) as unknown as typeof fetch;
229 const result = await healSuite({ repo: "o/r" });
230 expect(result.offline).toBe(false);
231 expect(result.flakyFound).toBe(2);
232 expect(result.prDraftBranch).toBe("gatetest/heal-42");
233 });
234});