c051ae0626
trivy fs scan failed the M0.2 CI gate on the skeleton commit because
next 15.0.3 has 9 known vulns (CRITICAL CVE-2025-29927 auth bypass in
middleware, plus 7 HIGH advisories). 16.2.6 is current latest and
covers every fixed-version range trivy listed.
Side effects of the major bump:
- next 16 dropped 'next lint' — switched the lint script to call eslint
directly ('eslint . --max-warnings 0').
- eslint-config-next 16 ships flat-config exports natively, so
eslint.config.mjs imports core-web-vitals + typescript directly
(no FlatCompat shim, no @eslint/eslintrc dep).
- Typed vi.fn<typeof fetch>() in tenant-registry.test to satisfy
stricter tuple inference under the new types.
All 4 gates green locally:
pnpm lint / typecheck / test --coverage (100% on src/lib) / build
Refs: M5.1 (skeleton)
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
import { fetchTenantBySlug, type Tenant } from "./tenant-registry";
|
|
|
|
const SAMPLE: Tenant = {
|
|
id: "00000000-0000-0000-0000-000000000001",
|
|
slug: "acme",
|
|
name: "Acme Inc.",
|
|
status: "active",
|
|
plan: "professional",
|
|
products: ["certifai", "compliance"],
|
|
created_at: "2026-05-18T22:00:00Z",
|
|
};
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
const originalRegistryUrl = process.env.TENANT_REGISTRY_URL;
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
process.env.TENANT_REGISTRY_URL = originalRegistryUrl;
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe("fetchTenantBySlug", () => {
|
|
beforeEach(() => {
|
|
process.env.TENANT_REGISTRY_URL = "http://test:1234";
|
|
});
|
|
|
|
test("200 → parsed tenant", async () => {
|
|
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify(SAMPLE), { status: 200 }));
|
|
const t = await fetchTenantBySlug("acme");
|
|
expect(t).toEqual(SAMPLE);
|
|
});
|
|
|
|
test("404 → null", async () => {
|
|
globalThis.fetch = vi.fn(async () => new Response("", { status: 404 }));
|
|
const t = await fetchTenantBySlug("nope");
|
|
expect(t).toBeNull();
|
|
});
|
|
|
|
test("500 → throws", async () => {
|
|
globalThis.fetch = vi.fn(async () => new Response("", { status: 500, statusText: "boom" }));
|
|
await expect(fetchTenantBySlug("acme")).rejects.toThrow(/tenant-registry: 500/);
|
|
});
|
|
|
|
test("falls back to default base URL when env unset", async () => {
|
|
delete process.env.TENANT_REGISTRY_URL;
|
|
const fetchSpy = vi.fn(async () => new Response(JSON.stringify(SAMPLE), { status: 200 }));
|
|
globalThis.fetch = fetchSpy;
|
|
await fetchTenantBySlug("acme");
|
|
expect(fetchSpy).toHaveBeenCalledWith(
|
|
"http://localhost:8080/v1/tenants/by-slug/acme",
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
test("encodes slug to defend against weird input", async () => {
|
|
const fetchSpy = vi.fn<typeof fetch>(async () => new Response("", { status: 404 }));
|
|
globalThis.fetch = fetchSpy;
|
|
await fetchTenantBySlug("a/b c");
|
|
const firstCall = fetchSpy.mock.calls[0];
|
|
expect(firstCall).toBeDefined();
|
|
expect(firstCall![0]).toBe("http://test:1234/v1/tenants/by-slug/a%2Fb%20c");
|
|
});
|
|
});
|