CI / ci (push) Canceled after 0s
install token now shown in dashboard until install completes.
147 lines
6.3 KiB
TypeScript
147 lines
6.3 KiB
TypeScript
import { mkdtemp, rm, writeFile, mkdir } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { fetchInstallationStatus, getInstallationToken, resolveInstallationView } from "../installation-status";
|
|
|
|
describe("fetchInstallationStatus", () => {
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
|
|
it("reports installed with installedAt when the tenant's /api/health says installed: true", async () => {
|
|
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ status: "healthy", database: "reachable", installed: true, installedAt: "2026-01-15T10:00:00.000Z" }),
|
|
}) as unknown as typeof fetch;
|
|
|
|
const result = await fetchInstallationStatus("acme");
|
|
expect(result).toEqual({ status: "installed", installedAt: "2026-01-15T10:00:00.000Z" });
|
|
});
|
|
|
|
it("reports not_installed (with no installedAt) when installed: false", async () => {
|
|
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ status: "healthy", database: "reachable", installed: false, installedAt: null }),
|
|
}) as unknown as typeof fetch;
|
|
|
|
const result = await fetchInstallationStatus("acme");
|
|
expect(result).toEqual({ status: "not_installed", installedAt: null });
|
|
});
|
|
|
|
it("reports unknown — never not_installed — when the fetch throws (container down/unreachable)", async () => {
|
|
globalThis.fetch = vi.fn().mockRejectedValue(new Error("ECONNREFUSED"));
|
|
|
|
const result = await fetchInstallationStatus("acme");
|
|
expect(result).toEqual({ status: "unknown", installedAt: null });
|
|
});
|
|
|
|
it("reports unknown when the response isn't ok (e.g. 503 unhealthy)", async () => {
|
|
globalThis.fetch = vi.fn().mockResolvedValue({ ok: false, json: async () => ({}) }) as unknown as typeof fetch;
|
|
|
|
const result = await fetchInstallationStatus("acme");
|
|
expect(result).toEqual({ status: "unknown", installedAt: null });
|
|
});
|
|
|
|
it("reports unknown for a malformed body (no boolean `installed` field)", async () => {
|
|
globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ unexpected: true }) }) as unknown as typeof fetch;
|
|
|
|
const result = await fetchInstallationStatus("acme");
|
|
expect(result).toEqual({ status: "unknown", installedAt: null });
|
|
});
|
|
|
|
it("targets the internal docker network hostname for the given slug, not the public domain", async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ installed: false }) });
|
|
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
|
|
|
await fetchInstallationStatus("acme-barbers");
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith("http://next-chair-acme-barbers:3000/api/health", expect.any(Object));
|
|
});
|
|
});
|
|
|
|
describe("getInstallationToken / resolveInstallationView", () => {
|
|
let stacksDir: string;
|
|
const originalStacksDir = process.env.STACKS_DIR;
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
beforeEach(async () => {
|
|
stacksDir = await mkdtemp(path.join(tmpdir(), "nc-manager-test-"));
|
|
process.env.STACKS_DIR = stacksDir;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
globalThis.fetch = originalFetch;
|
|
if (originalStacksDir === undefined) delete process.env.STACKS_DIR;
|
|
else process.env.STACKS_DIR = originalStacksDir;
|
|
await rm(stacksDir, { recursive: true, force: true });
|
|
});
|
|
|
|
async function writeStackEnv(slug: string, envContent: string) {
|
|
const dir = path.join(stacksDir, slug);
|
|
await mkdir(dir, { recursive: true });
|
|
await writeFile(path.join(dir, ".env"), envContent, "utf8");
|
|
}
|
|
|
|
it("reads the existing token straight from that business's own .env — an already-generated token is preserved, not regenerated", async () => {
|
|
await writeStackEnv("acme", 'BUSINESS_NAME="Acme"\nINSTALLATION_TOKEN="existing-token-value"\n');
|
|
|
|
const token = await getInstallationToken("acme");
|
|
expect(token).toBe("existing-token-value");
|
|
});
|
|
|
|
it("returns null (not an error) for a business with no token in its .env", async () => {
|
|
await writeStackEnv("legacy-biz", 'BUSINESS_NAME="Legacy"\n');
|
|
|
|
const token = await getInstallationToken("legacy-biz");
|
|
expect(token).toBeNull();
|
|
});
|
|
|
|
it("returns null for a business directory that doesn't exist", async () => {
|
|
const token = await getInstallationToken("does-not-exist");
|
|
expect(token).toBeNull();
|
|
});
|
|
|
|
it("tenant isolation: never returns another business's token", async () => {
|
|
await writeStackEnv("tenant-a", 'INSTALLATION_TOKEN="token-for-a"\n');
|
|
await writeStackEnv("tenant-b", 'INSTALLATION_TOKEN="token-for-b"\n');
|
|
|
|
expect(await getInstallationToken("tenant-a")).toBe("token-for-a");
|
|
expect(await getInstallationToken("tenant-b")).toBe("token-for-b");
|
|
});
|
|
|
|
it("resolveInstallationView never includes a token once installed — the token is not even read from disk in that branch", async () => {
|
|
await writeStackEnv("acme", 'INSTALLATION_TOKEN="should-never-appear"\n');
|
|
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ installed: true, installedAt: "2026-01-15T10:00:00.000Z" }),
|
|
}) as unknown as typeof fetch;
|
|
|
|
const view = await resolveInstallationView("acme", "acme.example.com");
|
|
|
|
expect(view).toEqual({ kind: "installed", installedAt: "2026-01-15T10:00:00.000Z" });
|
|
expect(JSON.stringify(view)).not.toContain("should-never-appear");
|
|
});
|
|
|
|
it("resolveInstallationView surfaces the token pre-install, with the public install URL", async () => {
|
|
await writeStackEnv("acme", 'INSTALLATION_TOKEN="real-token"\n');
|
|
globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ installed: false }) }) as unknown as typeof fetch;
|
|
|
|
const view = await resolveInstallationView("acme", "acme.example.com");
|
|
|
|
expect(view).toEqual({ kind: "not_installed", token: "real-token", installUrl: "https://acme.example.com/install" });
|
|
});
|
|
|
|
it("resolveInstallationView still surfaces the token (not hidden) when status can't be confirmed", async () => {
|
|
await writeStackEnv("acme", 'INSTALLATION_TOKEN="real-token"\n');
|
|
globalThis.fetch = vi.fn().mockRejectedValue(new Error("ECONNREFUSED"));
|
|
|
|
const view = await resolveInstallationView("acme", "acme.example.com");
|
|
|
|
expect(view.kind).toBe("unknown");
|
|
expect(view).toMatchObject({ token: "real-token" });
|
|
});
|
|
});
|