import { beforeEach, describe, expect, it, vi } from 'vitest' import { DebugEmlUploadError, uploadDebugEmlSuperAgentRun } from '@/services/debugEmlService' import { setUnauthorizedHandler } from '@/services/httpClient' const jsonHeaders = { headers: { get: (name: string) => (name.toLowerCase() === 'content-type' ? 'application/json' : null), }, } function mockJsonResponse(payload: unknown, status = 201): Response { return { ok: status >= 200 && status < 300, status, json: async () => payload, text: async () => JSON.stringify(payload), ...jsonHeaders, } as Response } describe('debugEmlService', () => { beforeEach(() => { vi.restoreAllMocks() setUnauthorizedHandler(null) }) it('uploads an eml file with FormData and debug key header', async () => { const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue( mockJsonResponse({ debug_run_id: '90001', source_message_id: '30001', source_provider: 'DEBUG_EML_UPLOAD', external_message_id: 'debug-eml-run-90001-abcdef123456', external_conversation_id: 'debug-thread-90001', original_eml_oss_url: 'https://oss.example/raw.eml', original_eml_sha256: 'sha256', uploaded_media: [], html_body_with_oss_urls: 'raw', html_body_sanitized: 'safe', html_sanitize_required: true, html_render_mode: 'SANITIZED_HTML', agentbus_like_payload: { schema_version: 'debug-eml-upload-v1' }, superagent_session_id: 'session-1', superagent_run_id: 'run-1', superagent_raw_answer: '{"ok":true}', superagent_parsed_json: { ok: true }, warnings: [], status: 'SUPERAGENT_SUCCEEDED', }), ) const file = new File(['From: guest@example.test'], 'booking.eml', { type: 'message/rfc822' }) const result = await uploadDebugEmlSuperAgentRun({ hotelId: 'HOTEL-TEST', debugUploadKey: ' manual-debug-key ', runLabel: 'frontend-smoke', file, }) expect(fetchMock).toHaveBeenCalledTimes(1) const [url, init] = fetchMock.mock.calls[0]! expect(url).toBe('/api/system/debug/eml-superagent-runs') expect(init).toMatchObject({ method: 'POST', headers: { Accept: 'application/json', 'X-TH-Hotel-Debug-Upload-Key': 'manual-debug-key', }, }) expect(init?.headers).not.toHaveProperty('Content-Type') expect(init?.body).toBeInstanceOf(FormData) const formData = init?.body as FormData expect(formData.get('hotel_id')).toBe('HOTEL-TEST') expect(formData.get('run_label')).toBe('frontend-smoke') expect(formData.get('file')).toBe(file) expect(result.debug_run_id).toBe('90001') }) it('throws a typed safe error for backend error_code responses', async () => { const unauthorizedHandler = vi.fn() setUnauthorizedHandler(unauthorizedHandler) vi.spyOn(globalThis, 'fetch').mockResolvedValue( mockJsonResponse( { error_code: 'DEBUG_UPLOAD_KEY_INVALID', message: 'Debug 上传访问口令缺失或错误。', }, 401, ), ) const file = new File(['From: guest@example.test'], 'booking.eml', { type: 'message/rfc822' }) await expect( uploadDebugEmlSuperAgentRun({ hotelId: 'HOTEL-TEST', debugUploadKey: 'wrong-key', file, }), ).rejects.toMatchObject({ status: 401, errorCode: 'DEBUG_UPLOAD_KEY_INVALID', message: 'Debug 上传访问口令缺失或错误。', } satisfies Partial) expect(unauthorizedHandler).not.toHaveBeenCalled() }) it('runs the shared unauthorized handler for auth session errors', async () => { const unauthorizedHandler = vi.fn() setUnauthorizedHandler(unauthorizedHandler) vi.spyOn(globalThis, 'fetch').mockResolvedValue( mockJsonResponse( { error_code: 'AUTH_SESSION_INVALID', message: '登录已失效。', }, 401, ), ) const file = new File(['From: guest@example.test'], 'booking.eml', { type: 'message/rfc822' }) await expect( uploadDebugEmlSuperAgentRun({ hotelId: 'HOTEL-TEST', debugUploadKey: 'manual-debug-key', file, }), ).rejects.toMatchObject({ status: 401, errorCode: 'AUTH_SESSION_INVALID', } satisfies Partial) expect(unauthorizedHandler).toHaveBeenCalledOnce() }) })