import { beforeEach, describe, expect, it } from 'vitest';
import { handleActuateAPI } from '../../api/index.js';
import { initDB } from '../../db.js';
import { generateApiKey } from '../../security/api-key-enhanced.js';
// Make sure the API surface that authenticates via API keys actually:
//   1. accepts an `Authorization: Bearer act_sk_*` token without CSRF,
//   2. enforces the key's scopes (collection + action),
//   3. rejects revoked/expired keys.
//
// We don't spin up the real database for these — a mock Prisma client that
// answers `apiKey.findUnique` and a minimal `document` interface is enough
// to exercise the auth + CSRF skip + scope check paths.
const VALID_SECRET = 'a'.repeat(48);
function createMockDB(opts) {
    const apiKey = opts.apiKey;
    return {
        apiKey: {
            // `hasModel` probes for `findMany` to decide whether the model exists
            // on the Prisma client. Including a stub here is enough to clear the
            // check even though our tests only exercise findUnique/update.
            findMany: async () => (apiKey ? [apiKey] : []),
            findUnique: async ({ where }) => {
                if (!apiKey)
                    return null;
                if (apiKey.keyHash !== where.keyHash)
                    return null;
                return apiKey;
            },
            update: async () => apiKey,
        },
        document: {
            findMany: async () => [],
            count: async () => 0,
            create: async ({ data }) => ({
                ...data,
                id: 'doc-1',
                createdAt: new Date(),
                updatedAt: new Date(),
            }),
        },
        user: {},
        session: {},
        media: {},
    };
}
async function makeKey(scopes) {
    const { key, keyHash, keyPrefix } = await generateApiKey({ prefix: 'act_sk', scopes });
    return {
        key,
        record: {
            id: 'apikey-1',
            keyHash,
            keyPrefix,
            userId: 'user-1',
            scopes,
            ipRestrictions: null,
            expiresAt: null,
            lastUsedAt: null,
            revokedAt: null,
        },
    };
}
describe('API key authentication', () => {
    beforeEach(() => {
        delete globalThis.__actuateConfig;
        process.env.CMS_SECRET = VALID_SECRET;
    });
    it('allows a request authenticated by a valid API key (no CSRF token)', async () => {
        const { key, record } = await makeKey({ collections: ['posts'], actions: ['read'] });
        const db = createMockDB({ apiKey: record });
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: {
                collections: {
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                },
            },
        });
        const response = await handler(new Request('https://example.com/api/cms/collections/posts', {
            headers: { Authorization: `Bearer ${key}` },
        }));
        expect(response.status).toBe(200);
    });
    it('rejects with 401 when bearer token does not match any API key in the DB', async () => {
        const { record } = await makeKey({ admin: true });
        const db = createMockDB({ apiKey: record });
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: {
                collections: {
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                },
            },
        });
        const fakeKey = 'act_sk_' + 'f'.repeat(64);
        const response = await handler(new Request('https://example.com/api/cms/collections/posts', {
            headers: { Authorization: `Bearer ${fakeKey}` },
        }));
        expect(response.status).toBe(401);
    });
    it('rejects revoked API keys', async () => {
        const { key, record } = await makeKey({ admin: true });
        record.revokedAt = new Date();
        const db = createMockDB({ apiKey: record });
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: {
                collections: {
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                },
            },
        });
        const response = await handler(new Request('https://example.com/api/cms/collections/posts', {
            headers: { Authorization: `Bearer ${key}` },
        }));
        expect(response.status).toBe(401);
    });
    it('rejects expired API keys', async () => {
        const { key, record } = await makeKey({ admin: true });
        record.expiresAt = new Date(Date.now() - 60_000);
        const db = createMockDB({ apiKey: record });
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: {
                collections: {
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                },
            },
        });
        const response = await handler(new Request('https://example.com/api/cms/collections/posts', {
            headers: { Authorization: `Bearer ${key}` },
        }));
        expect(response.status).toBe(401);
    });
    it('rejects with 403 when scope does not include the requested collection', async () => {
        const { key, record } = await makeKey({ collections: ['posts'], actions: ['read'] });
        const db = createMockDB({ apiKey: record });
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: {
                collections: {
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const response = await handler(new Request('https://example.com/api/cms/collections/pages', {
            headers: { Authorization: `Bearer ${key}` },
        }));
        expect(response.status).toBe(403);
    });
    it('rejects with 403 when scope does not include the requested action', async () => {
        const { key, record } = await makeKey({ collections: ['posts'], actions: ['read'] });
        const db = createMockDB({ apiKey: record });
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: {
                collections: {
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                },
            },
        });
        // POST should be blocked without `create` in actions — and crucially the
        // request has no CSRF token, so we're confirming that the API-key path
        // bypassed CSRF and got as far as scope enforcement before failing.
        const response = await handler(new Request('https://example.com/api/cms/collections/posts', {
            method: 'POST',
            headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
            body: JSON.stringify({ title: 'Hello' }),
        }));
        expect(response.status).toBe(403);
    });
    it('CSRF middleware does not block API-key POST requests', async () => {
        // Same shape as the previous test but with create scope — the request
        // should make it past CSRF, past scope, and at least into the action
        // handler. A 500/400 from the action layer here would still mean the
        // CSRF check passed (which is what we're asserting).
        const { key, record } = await makeKey({
            collections: ['posts'],
            actions: ['create'],
            admin: false,
        });
        const db = createMockDB({ apiKey: record });
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: {
                collections: {
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                },
            },
        });
        const response = await handler(new Request('https://example.com/api/cms/collections/posts', {
            method: 'POST',
            headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
            body: JSON.stringify({ title: 'Hello' }),
        }));
        // Should not be a 403 CSRF rejection.
        expect(response.status).not.toBe(403);
        if (response.status === 403) {
            const body = await response.json().catch(() => ({}));
            expect(body.error).not.toBe('Invalid CSRF token');
        }
    });
    it('non-API-key requests still require CSRF for POST', async () => {
        const db = createMockDB({});
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: {
                collections: {
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                },
            },
        });
        const response = await handler(new Request('https://example.com/api/cms/collections/posts', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ title: 'Hello' }),
        }));
        expect(response.status).toBe(403);
        const body = await response.json().catch(() => ({}));
        expect(body.error).toBe('Invalid CSRF token');
    });
    it('admin scope grants access to any collection', async () => {
        const { key, record } = await makeKey({ admin: true });
        const db = createMockDB({ apiKey: record });
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: {
                collections: {
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const a = await handler(new Request('https://example.com/api/cms/collections/posts', {
            headers: { Authorization: `Bearer ${key}` },
        }));
        const b = await handler(new Request('https://example.com/api/cms/collections/pages', {
            headers: { Authorization: `Bearer ${key}` },
        }));
        expect(a.status).toBe(200);
        expect(b.status).toBe(200);
    });
});
//# sourceMappingURL=api-key-auth.test.js.map