import { jsx as _jsx } from "react/jsx-runtime";
// @vitest-environment happy-dom
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
// The Security tab reads `/security` (computed posture + summaries + editable
// policy), saves via PUT `/security`, and revokes sessions via POST.
let securityData;
/** When set, PUT /security returns this instead of echoing the body. */
let putResponseOverride = null;
const cmsApi = vi.fn(async (endpoint, options) => {
    const method = options?.method ?? 'GET';
    if (endpoint === '/security' && method === 'GET')
        return { data: securityData, status: 200 };
    if (endpoint === '/security' && method === 'PUT') {
        const body = JSON.parse(String(options?.body ?? '{}'));
        if (putResponseOverride)
            return putResponseOverride(body);
        securityData = {
            ...securityData,
            settings: {
                mfa: { required: body.mfa.required, gracePeriodDays: body.mfa.gracePeriodDays },
                session: body.session,
                access: body.access,
            },
        };
        return { data: securityData, status: 200 };
    }
    if (endpoint === '/security/sessions/revoke-all' && method === 'POST')
        return { data: { revoked: 2 }, status: 200 };
    return { data: {}, status: 200 };
});
vi.mock('../../lib/api.js', () => ({ cmsApi: (e, o) => cmsApi(e, o) }));
const { SecurityTab } = await import('../../views/settings/SecurityTab.js');
function baseData(overrides = {}) {
    return {
        settings: {
            mfa: { required: false, gracePeriodDays: 7 },
            session: { maxConcurrentSessions: 5, timeoutHours: 168, idleLogoutEnabled: false },
            access: { ipAllowlist: [], auditLoggingEnabled: true },
        },
        status: {
            status: 'needs_attention',
            score: 88,
            label: 'Needs attention',
            description: 'Some recommended controls are missing.',
            checks: [
                { key: 'mfa', label: 'MFA coverage', status: 'warning', value: '1 of 2 admins' },
                { key: 'csrf', label: 'CSRF protection', status: 'pass', value: 'Enabled' },
            ],
            warnings: [
                {
                    id: 'mfa-not-required',
                    severity: 'warning',
                    category: 'auth',
                    title: 'MFA is not required in production',
                    description: 'Requiring MFA reduces account-takeover risk.',
                },
            ],
            lastCheckedAt: '2026-06-01T00:00:00.000Z',
        },
        users: {
            totalUsers: 3,
            totalAdmins: 2,
            withMfa: 1,
            withoutMfa: 2,
            adminsWithoutMfa: 1,
            inactive: 0,
        },
        apiKeys: { active: 1, withoutExpiration: 0, broadScope: 0, staleUnused90d: 0 },
        secrets: {
            cmsSecret: 'pass',
            encryptionKey: 'pass',
            rateLimitBackend: 'distributed',
            apiKeysHashed: true,
            webhookSecretsEncrypted: true,
        },
        ipAllowlist: {
            enabled: false,
            source: 'none',
            ranges: [],
            configRanges: [],
            currentIp: '1.2.3.4',
            currentIpAllowed: null,
        },
        failedLogins24h: 0,
        activeSessions: 2,
        csrfEnabled: true,
        uploadValidation: true,
        auditEnabled: true,
        sessionMaxAgeHours: 168,
        viewerHasMfa: true,
        environment: 'production',
        sources: {
            ipAllowlist: { source: 'config', editable: false },
            csrf: { source: 'computed', editable: false },
            rateLimiting: { source: 'environment', editable: false },
            uploadValidation: { source: 'computed', editable: false },
            cmsSecret: { source: 'environment', editable: false },
            encryptionKey: { source: 'environment', editable: false },
            audit: { source: 'computed', editable: false },
        },
        ...overrides,
    };
}
beforeEach(() => {
    cmsApi.mockClear();
    securityData = baseData();
    putResponseOverride = null;
});
describe('SecurityTab', () => {
    it('renders the status card and all security sections with a disabled save bar', async () => {
        render(_jsx(SecurityTab, { role: "ADMIN" }));
        await screen.findByText('Security Status');
        expect(screen.getByText('Authentication & MFA')).toBeTruthy();
        expect(screen.getByText('Sessions')).toBeTruthy();
        expect(screen.getByText('Access Control')).toBeTruthy();
        expect(screen.getByText('Secrets, Encryption & Audit')).toBeTruthy();
        // New design controls are present.
        expect(screen.getByLabelText(/Session timeout/)).toBeTruthy();
        expect(screen.getByRole('switch', { name: 'Idle auto-logout' })).toBeTruthy();
        expect(screen.getByLabelText(/Allowed IP addresses/)).toBeTruthy();
        expect(screen.getByRole('switch', { name: 'Audit logging' })).toBeTruthy();
        // Status badge + warning surfaced.
        expect(screen.getByText('Needs attention')).toBeTruthy();
        expect(screen.getByText('MFA is not required in production')).toBeTruthy();
        expect(screen.getByRole('button', { name: 'Save Changes' }).disabled).toBe(true);
    });
    it('enables Save when concurrent-session cap changes and persists via PUT /security', async () => {
        render(_jsx(SecurityTab, { role: "ADMIN" }));
        await screen.findByText('Security Status');
        const select = screen.getByLabelText(/Maximum concurrent sessions/);
        fireEvent.change(select, { target: { value: '3' } });
        const save = screen.getByRole('button', { name: 'Save Changes' });
        expect(save.disabled).toBe(false);
        fireEvent.click(save);
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/security', expect.objectContaining({
            method: 'PUT',
            body: expect.stringContaining('"maxConcurrentSessions":3'),
        })));
    });
    it('blocks enabling Require MFA (lockout-safe) when the acting admin has no MFA', async () => {
        securityData = baseData({ viewerHasMfa: false });
        render(_jsx(SecurityTab, { role: "ADMIN" }));
        await screen.findByText('Security Status');
        const toggle = screen.getByRole('switch', {
            name: 'Require MFA for admin users',
        });
        // Toggle is disabled until the admin sets up their own MFA.
        expect(toggle.disabled).toBe(true);
        expect(screen.getByText(/Set up MFA on your own account/i)).toBeTruthy();
    });
    it('confirms before requiring MFA and then persists it (admin has MFA)', async () => {
        render(_jsx(SecurityTab, { role: "ADMIN" }));
        await screen.findByText('Security Status');
        fireEvent.click(screen.getByRole('switch', { name: 'Require MFA for admin users' }));
        // Confirmation dialog appears; nothing saved yet.
        const confirm = await screen.findByRole('button', { name: 'Require MFA' });
        fireEvent.click(confirm);
        const save = screen.getByRole('button', { name: 'Save Changes' });
        expect(save.disabled).toBe(false);
        fireEvent.click(save);
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/security', expect.objectContaining({
            method: 'PUT',
            body: expect.stringContaining('"required":true'),
        })));
    });
    it('revokes other sessions through a confirmation flow', async () => {
        render(_jsx(SecurityTab, { role: "ADMIN" }));
        await screen.findByText('Security Status');
        fireEvent.click(screen.getByRole('button', { name: 'Revoke other sessions' }));
        const confirm = await screen.findByRole('button', { name: 'Revoke sessions' });
        fireEvent.click(confirm);
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/security/sessions/revoke-all', expect.objectContaining({ method: 'POST' })));
        await screen.findByText('Other sessions revoked.');
    });
    it('persists the session timeout and idle auto-logout policy', async () => {
        render(_jsx(SecurityTab, { role: "ADMIN" }));
        await screen.findByText('Security Status');
        fireEvent.change(screen.getByLabelText(/Session timeout/), { target: { value: '8' } });
        fireEvent.click(screen.getByRole('switch', { name: 'Idle auto-logout' }));
        fireEvent.click(screen.getByRole('button', { name: 'Save Changes' }));
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/security', expect.objectContaining({
            method: 'PUT',
            body: expect.stringContaining('"timeoutHours":8'),
        })));
        const putBody = JSON.parse(String(cmsApi.mock.calls.find((c) => c[1]?.method === 'PUT')?.[1]?.body));
        expect(putBody.session.idleLogoutEnabled).toBe(true);
    });
    it('persists the IP allowlist and audit toggle, flagging invalid entries client-side', async () => {
        render(_jsx(SecurityTab, { role: "ADMIN" }));
        await screen.findByText('Security Status');
        const textarea = screen.getByLabelText(/Allowed IP addresses/);
        fireEvent.change(textarea, { target: { value: 'not-an-ip' } });
        // Inline + summary validation blocks the save.
        expect((await screen.findAllByText(/not-an-ip/)).length).toBeGreaterThan(0);
        expect(screen.getByRole('button', { name: 'Save Changes' }).disabled).toBe(true);
        fireEvent.change(textarea, { target: { value: '203.0.113.0/24\n1.2.3.4' } });
        fireEvent.click(screen.getByRole('switch', { name: 'Audit logging' }));
        fireEvent.click(screen.getByRole('button', { name: 'Save Changes' }));
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/security', expect.anything()));
        const putBody = JSON.parse(String(cmsApi.mock.calls.find((c) => c[1]?.method === 'PUT')?.[1]?.body));
        expect(putBody.access.ipAllowlist).toEqual(['203.0.113.0/24', '1.2.3.4']);
        expect(putBody.access.auditLoggingEnabled).toBe(false);
    });
    it('surfaces the IP lockout guard as a confirm dialog and retries with confirmLockout', async () => {
        let rejected = false;
        putResponseOverride = (body) => {
            if (body.confirmLockout === true) {
                putResponseOverride = null;
                securityData = {
                    ...securityData,
                    settings: {
                        ...securityData.settings,
                        session: body.session,
                        access: body.access,
                    },
                };
                return { data: securityData, status: 200 };
            }
            rejected = true;
            return {
                error: 'Your current IP (1.2.3.4) is not in the allowlist — saving would lock you out.',
                code: 'IP_LOCKOUT_RISK',
                status: 409,
            };
        };
        render(_jsx(SecurityTab, { role: "ADMIN" }));
        await screen.findByText('Security Status');
        fireEvent.change(screen.getByLabelText(/Allowed IP addresses/), {
            target: { value: '10.0.0.0/8' },
        });
        fireEvent.click(screen.getByRole('button', { name: 'Save Changes' }));
        // The 409 turns into a confirm dialog, not a generic error.
        const confirm = await screen.findByRole('button', { name: 'Save anyway' });
        expect(rejected).toBe(true);
        fireEvent.click(confirm);
        await waitFor(() => {
            const confirmed = cmsApi.mock.calls.filter((c) => c[1]?.method === 'PUT' && String(c[1]?.body).includes('"confirmLockout":true'));
            expect(confirmed).toHaveLength(1);
        });
    });
    it('is read-only for non-admin roles', async () => {
        render(_jsx(SecurityTab, { role: "EDITOR" }));
        await screen.findByText('Security Status');
        expect(screen.getByText(/read-only access/i)).toBeTruthy();
        const toggle = screen.getByRole('switch', {
            name: 'Require MFA for admin users',
        });
        expect(toggle.disabled).toBe(true);
        const select = screen.getByLabelText(/Maximum concurrent sessions/);
        expect(select.disabled).toBe(true);
        const textarea = screen.getByLabelText(/Allowed IP addresses/);
        expect(textarea.disabled).toBe(true);
    });
});
//# sourceMappingURL=security-settings.render.test.js.map