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

platform-siblings.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.

platform-siblings.test.tsBlame139 lines · 1 contributor
f295f78Dictation App1/**
2 * Cross-product platform-status aggregator + admin widget smoke tests.
3 *
4 * The real siblings (crontech.ai, gluecron.com, gatetest.io) aren't
5 * reachable in test, so we stub `globalThis.fetch` to exercise the three
6 * branches: healthy JSON, non-2xx, and outright failure. Each branch must
7 * resolve — a sibling being down never crashes the widget.
8 */
9
10import { describe, it, expect, afterEach, beforeEach } from "bun:test";
11import app from "../app";
12import {
13 getSiblingStatuses,
14 siblingUrls,
15 __resetSiblingCache,
16} from "../lib/platform-siblings";
17
18const originalFetch = globalThis.fetch;
19
20beforeEach(() => {
21 __resetSiblingCache();
22});
23
24afterEach(() => {
25 globalThis.fetch = originalFetch;
26 __resetSiblingCache();
27 delete process.env.CRONTECH_STATUS_URL;
28 delete process.env.GLUECRON_STATUS_URL;
29 delete process.env.GATETEST_STATUS_URL;
30});
31
32describe("platform-siblings — siblingUrls", () => {
33 it("returns defaults pointing at production hosts", () => {
34 const urls = siblingUrls();
35 expect(urls.crontech).toBe("https://crontech.ai/api/platform-status");
36 expect(urls.gluecron).toBe("https://gluecron.com/api/platform-status");
37 expect(urls.gatetest).toBe("https://gatetest.io/api/platform-status");
38 });
39
40 it("honours env var overrides", () => {
41 process.env.CRONTECH_STATUS_URL = "https://example.com/ct";
42 process.env.GLUECRON_STATUS_URL = "https://example.com/gl";
43 process.env.GATETEST_STATUS_URL = "https://example.com/gt";
44 const urls = siblingUrls();
45 expect(urls.crontech).toBe("https://example.com/ct");
46 expect(urls.gluecron).toBe("https://example.com/gl");
47 expect(urls.gatetest).toBe("https://example.com/gt");
48 });
49});
50
51describe("platform-siblings — getSiblingStatuses", () => {
52 it("reports healthy when sibling returns healthy JSON", async () => {
53 globalThis.fetch = (async () =>
54 new Response(
55 JSON.stringify({
56 product: "x",
57 version: "1.2.3",
58 commit: "abcdef1234567",
59 healthy: true,
60 timestamp: "2026-04-20T00:00:00Z",
61 }),
62 { status: 200, headers: { "content-type": "application/json" } }
63 )) as unknown as typeof fetch;
64
65 const rows = await getSiblingStatuses({ force: true });
66 expect(rows).toHaveLength(3);
67 for (const r of rows) {
68 expect(r.reachable).toBe(true);
69 expect(r.healthy).toBe(true);
70 expect(r.version).toBe("1.2.3");
71 expect(r.commit).toBe("abcdef1234567");
72 expect(r.error).toBeNull();
73 expect(typeof r.latencyMs).toBe("number");
74 }
75 });
76
77 it("reports degraded (reachable but unhealthy) on non-2xx", async () => {
78 globalThis.fetch = (async () =>
79 new Response("nope", { status: 503 })) as unknown as typeof fetch;
80
81 const rows = await getSiblingStatuses({ force: true });
82 for (const r of rows) {
83 expect(r.reachable).toBe(true);
84 expect(r.healthy).toBe(false);
85 expect(r.error).toBe("HTTP 503");
86 }
87 });
88
89 it("reports unreachable on fetch error, never throws", async () => {
90 globalThis.fetch = (async () => {
91 throw new Error("ECONNREFUSED");
92 }) as unknown as typeof fetch;
93
94 const rows = await getSiblingStatuses({ force: true });
95 expect(rows).toHaveLength(3);
96 for (const r of rows) {
97 expect(r.reachable).toBe(false);
98 expect(r.healthy).toBe(false);
99 expect(r.error).toBeTruthy();
100 }
101 });
102
103 it("serves a cached result on the second call", async () => {
104 let calls = 0;
105 globalThis.fetch = (async () => {
106 calls++;
107 return new Response(
108 JSON.stringify({ healthy: true, version: "v", commit: "c" }),
109 { status: 200 }
110 );
111 }) as unknown as typeof fetch;
112
113 await getSiblingStatuses({ force: true });
114 expect(calls).toBe(3);
115 await getSiblingStatuses();
116 expect(calls).toBe(3);
117 });
118});
119
120describe("platform-status endpoint", () => {
121 it("GET /api/platform-status returns our own health JSON", async () => {
122 const res = await app.request("/api/platform-status");
123 expect(res.status).toBe(200);
124 const body = (await res.json()) as any;
125 expect(body.product).toBe("gluecron");
126 expect(body.healthy).toBe(true);
127 expect(typeof body.timestamp).toBe("string");
128 expect(body.siblings).toBeDefined();
129 expect(res.headers.get("access-control-allow-origin")).toBe("*");
130 });
131});
132
133describe("admin platform widget — auth gate", () => {
134 it("GET /admin/platform without auth → 302 /login", async () => {
135 const res = await app.request("/admin/platform");
136 expect(res.status).toBe(302);
137 expect(res.headers.get("location") || "").toContain("/login");
138 });
139});