import { generateCspNonce, buildCspHeader } from '../csp';

describe('generateCspNonce', () => {
    it('returns a base64 string', () => {
        const nonce = generateCspNonce();
        // Base64 of a UUID is 48 chars (uuid is 36 chars including dashes, base64 inflates by ~4/3).
        expect(nonce.length).toBeGreaterThanOrEqual(48);
        // Base64 alphabet.
        expect(nonce).toMatch(/^[A-Za-z0-9+/=]+$/);
    });

    it('returns a fresh value every call', () => {
        const a = generateCspNonce();
        const b = generateCspNonce();
        expect(a).not.toBe(b);
    });
});

describe('buildCspHeader', () => {
    const NONCE = 'TEST-NONCE-VALUE';

    it('prod mode: script-src has nonce + strict-dynamic, no unsafe-eval', () => {
        const csp = buildCspHeader(NONCE, false);
        expect(csp).toContain(`'nonce-${NONCE}'`);
        expect(csp).toContain("'strict-dynamic'");
        expect(csp).not.toMatch(/script-src[^;]*'unsafe-eval'/);
        expect(csp).not.toMatch(/script-src[^;]*'unsafe-inline'/);
    });

    it('dev mode: script-src adds unsafe-eval (React dev overlay)', () => {
        const csp = buildCspHeader(NONCE, true);
        expect(csp).toMatch(/script-src[^;]*'unsafe-eval'/);
        expect(csp).toContain(`'nonce-${NONCE}'`);
    });

    it('style-src keeps unsafe-inline for styled-jsx / runtime CSS-in-JS', () => {
        const csp = buildCspHeader(NONCE, false);
        expect(csp).toMatch(/style-src[^;]*'unsafe-inline'/);
        // Nonce is still emitted on style-src too so the framework can use it.
        expect(csp).toMatch(new RegExp(`style-src[^;]*'nonce-${NONCE}'`));
    });

    it('includes the baseline directives that were on the old static CSP', () => {
        const csp = buildCspHeader(NONCE, false);
        expect(csp).toContain("default-src 'self'");
        expect(csp).toContain("img-src 'self' data: blob: https:");
        expect(csp).toContain("font-src 'self' data:");
        expect(csp).toContain("connect-src 'self' https:");
        expect(csp).toContain("frame-ancestors 'self'");
        expect(csp).toContain("base-uri 'self'");
        expect(csp).toContain("form-action 'self'");
    });

    it('does not leak unsafe-eval or unsafe-inline into script-src in prod', () => {
        const csp = buildCspHeader(NONCE, false);
        const scriptDirective = csp.match(/script-src[^;]+/)?.[0] ?? '';
        expect(scriptDirective).not.toContain('unsafe-eval');
        expect(scriptDirective).not.toContain('unsafe-inline');
    });
});
