import { BUILTIN_COMMANDS, buildBuiltinCommandPrompt, COMMAND_INPUT_MAX_CHARS, CommandExecutionError, executeBuiltinCommand, getBuiltinCommand, buildSyncedCommandPrompt, normalizeSyncedCommand, } from '../src/features/commands/command-service' const originalFetch = global.fetch afterEach(() => { global.fetch = originalFetch }) describe('mobile built-in command service', () => { it('exposes an immutable, read-only catalog instead of fake CRUD data', () => { expect(Object.isFrozen(BUILTIN_COMMANDS)).toBe(true) expect(BUILTIN_COMMANDS).toHaveLength(4) expect(BUILTIN_COMMANDS.every((command) => Object.isFrozen(command))).toBe(true) expect(getBuiltinCommand('missing')).toBeNull() }) it('builds a bounded prompt from the selected verified command', () => { expect(buildBuiltinCommandPrompt('builtin-summarize', ' 실제 입력 ')) .toContain('실제 입력') expect(() => buildBuiltinCommandPrompt('missing', 'text')) .toThrow(CommandExecutionError) expect(() => buildBuiltinCommandPrompt('builtin-formal', ' ')) .toThrow(CommandExecutionError) expect(() => buildBuiltinCommandPrompt( 'builtin-formal', 'x'.repeat(COMMAND_INPUT_MAX_CHARS + 1), )).toThrow(CommandExecutionError) }) it('executes through the authenticated llm-proxy and returns only provider output', async () => { const generationId = '11111111-1111-4111-8111-111111111111' global.fetch = jest.fn().mockResolvedValue({ ok: true, status: 200, headers: { get: (name: string) => name.toLowerCase() === 'x-d3ro-generation-id' ? generationId : null }, json: async () => ({ content: [{ type: 'text', text: '서버 결과' }] }), }) await expect(executeBuiltinCommand('builtin-formal', '원문', { accessToken: 'signed-access-token', model: 'claude-haiku-4-5-20251001', })).resolves.toEqual({ text: '서버 결과', generationId }) expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining('/functions/v1/llm-proxy'), expect.objectContaining({ method: 'POST', headers: expect.objectContaining({ Authorization: 'Bearer signed-access-token', 'X-D3RO-Generation-Purpose': 'command_response', }), }), ) }) it('fails closed on provider errors without returning the original input', async () => { global.fetch = jest.fn().mockResolvedValue({ ok: false, status: 503, json: async () => ({ error: 'provider_unavailable' }), }) await expect(executeBuiltinCommand('builtin-translate-en', '민감한 원문', { accessToken: 'signed-access-token', })).rejects.toMatchObject({ code: 'PROVIDER_UNAVAILABLE', retryable: true, status: 503, }) }) it('normalizes the server command SSOT and builds bounded custom prompts', () => { const command = normalizeSyncedCommand({ id: '00000000-0000-4000-8000-000000000011', user_id: '00000000-0000-4000-8000-000000000012', builtin_key: null, name: 'Release notes', description: 'Summarize a release', prompt: 'Summarize without inventing facts: {{text}}', icon: 'sparkles', sort_order: 100, revision: 2, created_at: '2026-08-21T00:00:00Z', updated_at: '2026-08-21T00:01:00Z', }, '00000000-0000-4000-8000-000000000012') expect(command).toMatchObject({ builtinKey: null, revision: 2, sortOrder: 100 }) expect(buildSyncedCommandPrompt(command, ' 실제 변경 내용 ')).toContain('실제 변경 내용') expect(() => normalizeSyncedCommand({ ...command, user_id: '00000000-0000-4000-8000-000000000099', }, '00000000-0000-4000-8000-000000000012')).toThrow('server') }) })