import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { handleActuateAPI } from '../../api/index.js';
import { setActuateConfig } from '../../config/runtime.js';
import { initDB } from '../../db.js';
const VALID_SECRET = 'a-real-cms-secret-of-at-least-32-characters';
describe('/api/cms/health status derivation', () => {
    // Snapshot every env var any test in this file mutates so we can restore
    // them in `afterEach`. NOTE: `CMS_SESSION_SECRET` is included even though
    // only one test sets it — Bugbot caught the original test where cleanup
    // ran inline and was therefore skipped on assertion failure, leaking the
    // legacy secret into the next `it.each` iteration and causing the
    // config-file path test to pass for the wrong reason.
    const original = {
        CMS_SECRET: process.env.CMS_SECRET,
        CMS_SESSION_SECRET: process.env.CMS_SESSION_SECRET,
        CMS_ENCRYPTION_KEY: process.env.CMS_ENCRYPTION_KEY,
        CRON_SECRET: process.env.CRON_SECRET,
        DATABASE_URL: process.env.DATABASE_URL,
    };
    beforeEach(() => {
        process.env.CMS_SECRET = VALID_SECRET;
        process.env.DATABASE_URL = 'postgres://x';
        delete process.env.CMS_SESSION_SECRET;
        delete process.env.CMS_ENCRYPTION_KEY;
        delete process.env.CRON_SECRET;
    });
    afterEach(() => {
        for (const [k, v] of Object.entries(original)) {
            if (v === undefined)
                delete process.env[k];
            else
                process.env[k] = v;
        }
        // Tests within this file share `globalThis`, so explicitly clear any
        // config a single test stashed (see `runtime.ts` doc — "Tests reset
        // between suites").
        delete globalThis.__actuateConfig;
    });
    // Bugbot review (PR #41): the early-return DB-failure path hardcoded
    // `status: 'degraded'`, ignoring the env validation result computed two
    // lines above. A deploy with both a broken DB *and* a malformed CMS_SECRET
    // would report `degraded` even though it's actually `unhealthy` —
    // defeating M6's whole point of catching env misconfig at /health time.
    it('returns "unhealthy" when env is misconfigured AND db() throws', async () => {
        process.env.CMS_SECRET = 'too-short'; // triggers env error
        // Force db() to throw by initializing with an object that the route's
        // model probes will treat as unusable while leaving db() itself callable.
        // The simplest way is to trip the catch by leaving DB uninitialized.
        initDB(null); // db() will throw "Prisma client not initialised"
        const handler = handleActuateAPI({ prismaClient: null });
        const response = await handler(new Request('https://example.com/api/cms/health', { method: 'GET' }));
        expect(response.status).toBe(200);
        const payload = (await response.json());
        expect(payload.data.databaseConnected).toBe(false);
        expect(payload.data.status).toBe('unhealthy');
    });
    it('returns "degraded" when only db() throws (env is fine)', async () => {
        initDB(null);
        const handler = handleActuateAPI({ prismaClient: null });
        const response = await handler(new Request('https://example.com/api/cms/health', { method: 'GET' }));
        const payload = (await response.json());
        expect(payload.data.status).toBe('degraded');
    });
    // Bugbot review (PR #41): the unauthenticated /health response embeds the
    // full env validation, so any "ok" message that mentions the configured
    // secret length leaks that length to anonymous callers. Validate the
    // serialized response here so a regression in either the env module or the
    // health route is caught.
    it('does not leak exact secret lengths in the /health JSON', async () => {
        process.env.CMS_SECRET = 'a'.repeat(45);
        process.env.CRON_SECRET = 'a-real-cron-secret-with-known-length';
        process.env.CMS_ENCRYPTION_KEY = 'a1b2'.repeat(16);
        initDB({
            document: { count: async () => 0 },
            user: { count: async () => 0 },
            media: { count: async () => 0 },
        });
        const handler = handleActuateAPI({
            prismaClient: {
                document: { count: async () => 0 },
                user: { count: async () => 0 },
                media: { count: async () => 0 },
            },
        });
        const response = await handler(new Request('https://example.com/api/cms/health', { method: 'GET' }));
        const text = await response.text();
        // No "Configured (N chars)." phrasing anywhere in the body. The regex is
        // broad enough to catch both "42 chars" and "42 chars." variants.
        expect(text).not.toMatch(/\d+\s*chars?/i);
        // The configured secret length (45) must not leak. Scope this to the env
        // validation messages — where a leak would actually surface — so it can't
        // collide with unrelated numbers elsewhere in the payload (e.g. the package
        // version "0.45.0", which legitimately contains "45").
        const payload = JSON.parse(text);
        const messages = payload.data.env.checks.map((c) => c.message).join('\n');
        expect(messages).not.toContain('45');
    });
    // Bugbot review (PR #43): `validateEnvShape()` only inspected
    // `process.env.CMS_SECRET`, but `getSessionSecret()` also accepts the
    // secret from the legacy `CMS_SESSION_SECRET` env var or `actuate.config.ts
    // → secret`. Without the wrapping the route now applies, a deploy that
    // configures the secret via the config file would get the contradictory
    // response `secretConfigured: true` AND `status: "unhealthy"`, tripping
    // monitoring / load-balancer health probes for no real fault.
    it.each([
        [
            'CMS_SESSION_SECRET (legacy env var)',
            () => {
                delete process.env.CMS_SECRET;
                process.env.CMS_SESSION_SECRET = 'a'.repeat(40);
            },
        ],
        [
            'actuate.config.ts → secret (config-file path)',
            () => {
                delete process.env.CMS_SECRET;
                setActuateConfig({ secret: 'a'.repeat(40) });
            },
        ],
    ])('does not report unhealthy when CMS_SECRET is supplied via %s', async (_label, setup) => {
        setup();
        initDB({
            document: { count: async () => 0 },
            user: { count: async () => 0 },
            media: { count: async () => 0 },
        });
        const handler = handleActuateAPI({
            prismaClient: {
                document: { count: async () => 0 },
                user: { count: async () => 0 },
                media: { count: async () => 0 },
            },
        });
        const response = await handler(new Request('https://example.com/api/cms/health', { method: 'GET' }));
        const payload = (await response.json());
        // Both signals must agree — no contradictory `secretConfigured: true`
        // alongside `status: "unhealthy"`.
        expect(payload.data.secretConfigured).toBe(true);
        expect(payload.data.status).not.toBe('unhealthy');
        const cmsSecretCheck = payload.data.env.checks.find((c) => c.name === 'CMS_SECRET');
        expect(cmsSecretCheck?.status).toBe('ok');
    });
});
//# sourceMappingURL=health.test.js.map