import { describe, expect, it } from 'vitest';
import { DEFAULT_SECURITY_SETTINGS, computeSecurityStatus, mfaEnableLockoutError, parseSecuritySettings, summarizeApiKeys, summarizeUsers, validateIpRange, validateSecuritySettings, } from '../../security/center/index.js';
function baseInput(overrides = {}) {
    return {
        isProduction: true,
        settings: {
            mfa: { required: true, gracePeriodDays: 7 },
            session: { maxConcurrentSessions: 5 },
        },
        users: {
            totalUsers: 3,
            totalAdmins: 2,
            withMfa: 2,
            withoutMfa: 1,
            adminsWithoutMfa: 0,
            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: [],
            currentIp: '1.2.3.4',
            currentIpAllowed: null,
        },
        failedLogins24h: 0,
        activeSessions: 1,
        csrfEnabled: true,
        uploadValidation: true,
        auditEnabled: true,
        ...overrides,
    };
}
describe('computeSecurityStatus', () => {
    it('reports secure when all controls pass', () => {
        const status = computeSecurityStatus(baseInput());
        expect(status.status).toBe('secure');
        expect(status.score).toBe(100);
        expect(status.warnings).toHaveLength(0);
        // Every advertised check is present.
        const keys = status.checks.map((c) => c.key);
        expect(keys).toEqual(expect.arrayContaining([
            'mfa',
            'sessions',
            'api_keys',
            'csrf',
            'rate_limit',
            'cms_secret',
            'audit',
        ]));
    });
    it('is at_risk when CSRF is disabled', () => {
        const status = computeSecurityStatus(baseInput({ csrfEnabled: false }));
        expect(status.status).toBe('at_risk');
        expect(status.warnings.some((w) => w.id === 'csrf-disabled' && w.severity === 'critical')).toBe(true);
        expect(status.score).toBeLessThan(100);
    });
    it('is at_risk when MFA is required but an admin has not enrolled', () => {
        const status = computeSecurityStatus(baseInput({
            users: {
                totalUsers: 3,
                totalAdmins: 2,
                withMfa: 1,
                withoutMfa: 1,
                adminsWithoutMfa: 1,
                inactive: 0,
            },
        }));
        expect(status.status).toBe('at_risk');
        expect(status.warnings.some((w) => w.id === 'mfa-required-gap')).toBe(true);
        const mfaCheck = status.checks.find((c) => c.key === 'mfa');
        expect(mfaCheck?.status).toBe('fail');
    });
    it('is at_risk in production when CMS_SECRET is missing', () => {
        const status = computeSecurityStatus(baseInput({ secrets: { ...baseInput().secrets, cmsSecret: 'fail' } }));
        expect(status.status).toBe('at_risk');
        expect(status.warnings.some((w) => w.id === 'cms-secret-missing')).toBe(true);
    });
    it('needs_attention when MFA is not required in production', () => {
        const status = computeSecurityStatus(baseInput({
            settings: {
                mfa: { required: false, gracePeriodDays: 7 },
                session: { maxConcurrentSessions: 5 },
            },
        }));
        expect(status.status).toBe('needs_attention');
        expect(status.warnings.some((w) => w.id === 'mfa-not-required')).toBe(true);
    });
    it('warns (not critical) about in-memory rate limiting in production', () => {
        const status = computeSecurityStatus(baseInput({ secrets: { ...baseInput().secrets, rateLimitBackend: 'in_memory' } }));
        expect(status.status).toBe('needs_attention');
        expect(status.warnings.some((w) => w.id === 'rate-limit-in-memory')).toBe(true);
    });
    it('is unknown when secret state cannot be determined', () => {
        const status = computeSecurityStatus(baseInput({ secrets: { ...baseInput().secrets, cmsSecret: 'unknown' } }));
        expect(status.status).toBe('unknown');
    });
    it('flags broad-scope API keys as a failing check', () => {
        const status = computeSecurityStatus(baseInput({ apiKeys: { active: 2, withoutExpiration: 0, broadScope: 1, staleUnused90d: 0 } }));
        expect(status.checks.find((c) => c.key === 'api_keys')?.status).toBe('fail');
        expect(status.warnings.some((w) => w.id === 'api-keys-broad')).toBe(true);
    });
});
describe('validateSecuritySettings', () => {
    it('accepts valid settings', () => {
        expect(validateSecuritySettings(DEFAULT_SECURITY_SETTINGS)).toEqual([]);
    });
    it('rejects an out-of-range grace period', () => {
        const issues = validateSecuritySettings({
            mfa: { required: true, gracePeriodDays: 999 },
            session: { maxConcurrentSessions: 5 },
        });
        expect(issues.some((i) => i.field === 'mfaGracePeriodDays' && i.severity === 'error')).toBe(true);
    });
    it('rejects an out-of-range concurrent session cap', () => {
        const issues = validateSecuritySettings({
            mfa: { required: false, gracePeriodDays: 0 },
            session: { maxConcurrentSessions: -1 },
        });
        expect(issues.some((i) => i.field === 'maxConcurrentSessions')).toBe(true);
    });
});
describe('parseSecuritySettings', () => {
    it('returns defaults for an empty blob', () => {
        expect(parseSecuritySettings({})).toEqual(DEFAULT_SECURITY_SETTINGS);
    });
    it('coerces and clamps values', () => {
        const parsed = parseSecuritySettings({
            security: {
                mfa: { required: 'yes', gracePeriodDays: 9999 },
                session: { maxConcurrentSessions: -5 },
            },
        });
        expect(parsed.mfa.required).toBe(true);
        expect(parsed.mfa.gracePeriodDays).toBe(365);
        expect(parsed.session.maxConcurrentSessions).toBe(0);
    });
    it('preserves requiredAt when present', () => {
        const parsed = parseSecuritySettings({
            security: {
                mfa: { required: true, gracePeriodDays: 1, requiredAt: '2026-01-01T00:00:00.000Z' },
            },
        });
        expect(parsed.mfa.requiredAt).toBe('2026-01-01T00:00:00.000Z');
    });
});
describe('validateIpRange', () => {
    it.each([
        '192.168.1.1',
        '10.0.0.0/8',
        '172.16.0.0/12',
        '0.0.0.0/0',
        '2001:db8::1',
        '2001:db8::/32',
        '::1',
    ])('accepts %s', (entry) => {
        expect(validateIpRange(entry)).toBe(true);
    });
    it.each(['', 'not-an-ip', '999.1.1.1', '10.0.0.0/33', '1.2.3.4/abc', '2001:db8::/129', '1.2.3'])('rejects %s', (entry) => {
        expect(validateIpRange(entry)).toBe(false);
    });
});
describe('mfaEnableLockoutError', () => {
    it('blocks enabling MFA when the acting admin has no MFA', () => {
        expect(mfaEnableLockoutError({ enabling: true, viewerHasMfa: false, totalAdmins: 1 })).toMatch(/lock yourself out/i);
    });
    it('allows enabling when the acting admin has MFA', () => {
        expect(mfaEnableLockoutError({ enabling: true, viewerHasMfa: true, totalAdmins: 1 })).toBeNull();
    });
    it('never blocks when not enabling', () => {
        expect(mfaEnableLockoutError({ enabling: false, viewerHasMfa: false, totalAdmins: 3 })).toBeNull();
    });
});
describe('summarizeUsers', () => {
    it('counts admin MFA coverage and inactive accounts', () => {
        const summary = summarizeUsers([
            { role: 'ADMIN', isActive: true, totpEnabled: true },
            { role: 'ADMIN', isActive: true, totpEnabled: false },
            { role: 'EDITOR', isActive: true, totpEnabled: false },
            { role: 'ADMIN', isActive: false, totpEnabled: false },
        ]);
        expect(summary.totalUsers).toBe(4);
        expect(summary.totalAdmins).toBe(3);
        expect(summary.withMfa).toBe(1);
        expect(summary.adminsWithoutMfa).toBe(1); // inactive admin excluded
        expect(summary.inactive).toBe(1);
    });
});
describe('summarizeApiKeys', () => {
    const now = Date.parse('2026-06-01T00:00:00.000Z');
    it('classifies risk and excludes revoked/expired keys', () => {
        const summary = summarizeApiKeys([
            { scopes: { admin: true }, expiresAt: null, lastUsedAt: null, revokedAt: null },
            {
                scopes: { media: true },
                expiresAt: '2026-12-01T00:00:00.000Z',
                lastUsedAt: '2026-01-01T00:00:00.000Z',
                revokedAt: null,
            },
            {
                scopes: { media: true },
                expiresAt: '2020-01-01T00:00:00.000Z',
                lastUsedAt: null,
                revokedAt: null,
            }, // expired
            {
                scopes: { admin: true },
                expiresAt: null,
                lastUsedAt: null,
                revokedAt: '2026-01-01T00:00:00.000Z',
            }, // revoked
        ], now);
        expect(summary.active).toBe(2);
        expect(summary.broadScope).toBe(1);
        expect(summary.withoutExpiration).toBe(1);
        expect(summary.staleUnused90d).toBe(1); // key 2 last used > 90d before `now`
    });
});
//# sourceMappingURL=center.test.js.map