import { afterEach, describe, expect, it, mock } from 'bun:test';
/**
 * Tests for API client error handling.
 *
 * Ensures that error responses from the compiler API (JSON body with
 * error details, stderr, stdout, etc.) are surfaced in the thrown Error
 * message rather than swallowed.
 */
// Stub auth so callCompiler doesn't try to read real credentials
mock.module('./auth.js', () => ({
    getStoredToken: () => Promise.resolve('fake-token'),
}));
// We need to control fetch globally
const originalFetch = globalThis.fetch;
afterEach(() => {
    globalThis.fetch = originalFetch;
});
describe('callCompiler error responses', () => {
    function mockFetch(responses) {
        let call = 0;
        globalThis.fetch = (async () => {
            return responses[call++] ?? responses[responses.length - 1];
        });
    }
    // Health check returns ready, then the compile call returns the given response
    function mockHealthThenCompile(compileResponse) {
        let call = 0;
        globalThis.fetch = (async (url) => {
            if (call === 0 || (typeof url === 'string' && url.includes('/health'))) {
                call++;
                return new Response(JSON.stringify({ status: 'ready', version: '0.5.12' }), {
                    status: 200,
                    headers: { 'content-type': 'application/json' },
                });
            }
            call++;
            return compileResponse;
        });
    }
    const dummyInput = {
        config: { name: 'test', preset: 'cloudflare', template: 'hono', providers: {} },
        features: [],
    };
    it('includes error message from JSON 500 response', async () => {
        const { callCompiler } = await import('./api-client.js');
        mockHealthThenCompile(new Response(JSON.stringify({
            error: 'Compilation failed',
            message: 'Action "submit" uses roles: ["*"]. Use roles: ["PUBLIC"] instead.',
        }), { status: 500, statusText: 'Internal Server Error', headers: { 'content-type': 'application/json' } }));
        try {
            await callCompiler(dummyInput);
            expect.unreachable('should have thrown');
        }
        catch (err) {
            expect(err.message).toContain('Compilation failed');
            expect(err.message).toContain('500 Internal Server Error');
            // The actual error message from the compiler must be present
            expect(err.message).not.toBe('Compilation failed');
        }
    });
    it('includes details, stderr, and stdout from CompilationError response', async () => {
        const { callCompiler } = await import('./api-client.js');
        mockHealthThenCompile(new Response(JSON.stringify({
            error: 'Compilation failed',
            code: 'CLI_COMMAND_FAILED',
            message: 'Critical command failed: drizzle-kit generate',
            details: {
                command: 'drizzle-kit generate',
                exitCode: 1,
                purpose: 'Generate migration files',
                causes: ['Missing dependency'],
                fixes: ['Install deps'],
            },
            stderr: 'Error: Cannot find module drizzle-orm',
            stdout: 'Running drizzle-kit...',
        }), { status: 500, statusText: 'Internal Server Error', headers: { 'content-type': 'application/json' } }));
        try {
            await callCompiler(dummyInput);
            expect.unreachable('should have thrown');
        }
        catch (err) {
            expect(err.message).toContain('Compilation failed');
            expect(err.message).toContain('drizzle-kit generate');
            expect(err.message).toContain('Cannot find module drizzle-orm');
            expect(err.message).toContain('Running drizzle-kit...');
        }
    });
    it('includes raw text for non-JSON 500 responses', async () => {
        const { callCompiler } = await import('./api-client.js');
        // Non-JSON proxy errors get retried, so provide 3 identical responses
        let calls = 0;
        globalThis.fetch = (async (url) => {
            if (typeof url === 'string' && url.includes('/health')) {
                return new Response(JSON.stringify({ status: 'ready', version: '0.5.12' }), {
                    status: 200,
                    headers: { 'content-type': 'application/json' },
                });
            }
            calls++;
            return new Response('<html>Bad Gateway</html>', {
                status: 500,
                statusText: 'Internal Server Error',
                headers: { 'content-type': 'text/html' },
            });
        });
        try {
            await callCompiler(dummyInput);
            expect.unreachable('should have thrown');
        }
        catch (err) {
            // After all retries exhausted, the raw HTML should be in the error
            expect(err.message).toContain('Bad Gateway');
            expect(err.message).toContain('Response:');
        }
    });
    it('includes hint for 403 responses', async () => {
        const { callCompiler } = await import('./api-client.js');
        mockHealthThenCompile(new Response(JSON.stringify({
            error: '"pro-template" requires a Pro account',
            hint: 'Run: quickback login',
            upgrade: 'https://quickback.dev/pricing',
        }), { status: 403, statusText: 'Forbidden', headers: { 'content-type': 'application/json' } }));
        try {
            await callCompiler(dummyInput);
            expect.unreachable('should have thrown');
        }
        catch (err) {
            expect(err.message).toContain('requires a Pro account');
            expect(err.message).toContain('Run: quickback login');
        }
    });
    it('includes hint for 401 responses', async () => {
        const { callCompiler } = await import('./api-client.js');
        mockHealthThenCompile(new Response(JSON.stringify({
            error: 'Authentication required.',
            hint: 'Run: quickback login',
        }), { status: 401, statusText: 'Unauthorized', headers: { 'content-type': 'application/json' } }));
        try {
            await callCompiler(dummyInput);
            expect.unreachable('should have thrown');
        }
        catch (err) {
            expect(err.message).toContain('Authentication required.');
            expect(err.message).toContain('Run: quickback login');
        }
    });
    function sseResponse(events) {
        const body = events
            .map((e) => `event: ${e.event}\ndata: ${JSON.stringify(e.data)}\n\n`)
            .join('');
        return new Response(body, {
            status: 200,
            headers: { 'content-type': 'text/event-stream' },
        });
    }
    it('consumes v2 stream: meta + file events + done', async () => {
        const { callCompiler } = await import('./api-client.js');
        mockHealthThenCompile(sseResponse([
            { event: 'meta', data: { fileCount: 2, version: '0.6.0' } },
            { event: 'file', data: { path: 'src/a.ts', content: 'export const a = 1;', type: 'source' } },
            { event: 'file', data: { path: 'src/b.ts', content: 'export const b = 2;', type: 'source' } },
            {
                event: 'done',
                data: {
                    meta: {
                        fileCount: 2,
                        features: ['demo'],
                        timestamp: '2026-04-27T00:00:00.000Z',
                        version: '0.6.0',
                    },
                    commands: [],
                    warnings: [],
                },
            },
        ]));
        const result = await callCompiler(dummyInput);
        expect(result.files.length).toBe(2);
        expect(result.files[0].path).toBe('src/a.ts');
        expect(result.files[1].content).toContain('export const b');
        expect(result.meta.version).toBe('0.6.0');
    });
    it('falls back to v1 result event when server is older', async () => {
        const { callCompiler } = await import('./api-client.js');
        mockHealthThenCompile(sseResponse([
            {
                event: 'result',
                data: {
                    files: [{ path: 'src/legacy.ts', content: '// v1', type: 'source' }],
                    meta: {
                        fileCount: 1,
                        features: [],
                        timestamp: '2026-04-27T00:00:00.000Z',
                        version: '0.5.99',
                    },
                    commands: [],
                    warnings: [],
                },
            },
        ]));
        const result = await callCompiler(dummyInput);
        expect(result.files.length).toBe(1);
        expect(result.files[0].path).toBe('src/legacy.ts');
        expect(result.meta.version).toBe('0.5.99');
    });
});
//# sourceMappingURL=api-client.test.js.map