import { afterEach, 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';
import { setStorageAdapter } from '../../storage/index.js';
/**
 * Regression tests for the media upload storage contract (broken-image bug):
 *
 * The old `/media/upload` swallowed blob-storage failures and stored a
 * `/api/cms/media/file/<key>` fallback URL — a path no route serves — so the
 * API returned 201, a media record appeared in the library, but the bytes
 * were never persisted and every thumbnail 404'd.
 *
 * The contract now is:
 *   - storage failure (adapter throw, or no adapter + blob unavailable)
 *     → 500, and NO media record is created;
 *   - storage success → 201 with `data.url` set to the real public URL
 *     (the admin uses it for immediate previews) and `storageKey` equal to
 *     that URL — never a dead fallback path.
 */
const VALID_SECRET = 'a'.repeat(48);
// A 1×1 transparent PNG — valid magic bytes; uploaded with skipOptimize so the
// image pipeline (sharp) never runs.
const TINY_PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==', 'base64');
function createMockDB(apiKey) {
    const created = [];
    const db = {
        apiKey: {
            findMany: async () => [apiKey],
            findUnique: async ({ where }) => apiKey.keyHash === where.keyHash ? apiKey : null,
            update: async () => apiKey,
        },
        media: {
            create: async ({ data }) => {
                const row = { id: `media-${created.length + 1}`, ...data };
                created.push(row);
                return row;
            },
        },
        auditLog: {
            create: async () => ({}),
        },
        user: {},
        session: {},
    };
    return { db, created };
}
async function makeKey() {
    const { key, keyHash, keyPrefix } = await generateApiKey({
        prefix: 'act_sk',
        scopes: { admin: true },
    });
    return {
        key,
        record: {
            id: 'apikey-1',
            keyHash,
            keyPrefix,
            userId: 'user-1',
            scopes: { admin: true },
            ipRestrictions: null,
            expiresAt: null,
            lastUsedAt: null,
            revokedAt: null,
        },
    };
}
/**
 * Build a multipart upload request with a buffered body and an explicit
 * Content-Length (the route rejects length-less bodies up front), mirroring
 * what a real HTTP server sees.
 */
async function uploadRequest(key) {
    const form = new FormData();
    form.append('file', new File([TINY_PNG], 'logo.png', { type: 'image/png' }));
    form.append('skipOptimize', 'true');
    const draft = new Request('https://example.com/api/cms/media/upload', {
        method: 'POST',
        body: form,
    });
    const body = Buffer.from(await draft.arrayBuffer());
    const headers = new Headers(draft.headers);
    headers.set('content-length', String(body.byteLength));
    headers.set('Authorization', `Bearer ${key}`);
    return new Request(draft.url, { method: 'POST', headers, body });
}
describe('POST /media/upload — storage contract', () => {
    beforeEach(() => {
        delete globalThis.__actuateConfig;
        process.env.CMS_SECRET = VALID_SECRET;
        setStorageAdapter(null);
    });
    afterEach(() => {
        setStorageAdapter(null);
    });
    it('returns 201 with a real url + storageKey when the storage adapter succeeds', async () => {
        const { key, record } = await makeKey();
        const { db, created } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        const stored = [];
        setStorageAdapter({
            upload: async (storageKey) => {
                stored.push(storageKey);
                return `https://cdn.example.com/${storageKey}`;
            },
            download: async () => Buffer.alloc(0),
            delete: async () => { },
            getSignedUrl: async (k) => `https://cdn.example.com/${k}`,
        });
        const res = await handler(await uploadRequest(key));
        expect(res.status).toBe(201);
        const body = (await res.json());
        expect(stored).toHaveLength(1);
        expect(body.data.url).toBe(`https://cdn.example.com/${stored[0]}`);
        expect(body.data.storageKey).toBe(body.data.url);
        expect(created).toHaveLength(1);
        expect(created[0].storageKey).toBe(body.data.url);
    });
    it('returns 500 and creates NO media record when the storage adapter throws', async () => {
        const { key, record } = await makeKey();
        const { db, created } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        setStorageAdapter({
            upload: async () => {
                throw new Error('disk on fire');
            },
            download: async () => Buffer.alloc(0),
            delete: async () => { },
            getSignedUrl: async () => '',
        });
        const res = await handler(await uploadRequest(key));
        expect(res.status).toBe(500);
        expect(created).toHaveLength(0);
    });
    it('returns 500 and creates NO media record when no adapter is set and blob storage fails', async () => {
        // No storage adapter → the route falls through to @vercel/blob, which has
        // no BLOB_READ_WRITE_TOKEN in tests and must fail. The old code turned
        // this exact situation into a 201 + dead `/api/cms/media/file/…` URL.
        const { key, record } = await makeKey();
        const { db, created } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        const res = await handler(await uploadRequest(key));
        expect(res.status).toBe(500);
        const body = (await res.json());
        expect(String(body.error)).toContain('storage');
        expect(created).toHaveLength(0);
    });
    it('explains the storage shorthand gap when provider is set but no adapter is registered', async () => {
        // `storage: { provider: 'vercel-blob' }` reads like it works, but it only
        // describes intent — uploads need a registered adapter. The error must call
        // that out instead of blaming BLOB_READ_WRITE_TOKEN.
        const { key, record } = await makeKey();
        const { db, created } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({
            prismaClient: db,
            config: { collections: {}, platform: { storage: { provider: 'vercel-blob' } } },
        });
        const res = await handler(await uploadRequest(key));
        expect(res.status).toBe(500);
        const body = (await res.json());
        expect(String(body.error)).toContain('vercel-blob');
        expect(String(body.error)).toContain('no storage adapter is registered');
        expect(String(body.error)).not.toContain('BLOB_READ_WRITE_TOKEN');
        expect(created).toHaveLength(0);
    });
});
//# sourceMappingURL=media-upload-storage.test.js.map