import { describe, it, expect, vi, beforeEach } from 'vitest'; import { supabase } from '../supabase'; vi.mock('../supabase', () => { return { supabase: { functions: { invoke: vi.fn(), }, }, }; }); // Mock global fetch const mockFetch = vi.fn(); global.fetch = mockFetch; // Import code under test after mock definition import { invokeEdgeFunction, setUseGoGateway, setGoGatewayUrl } from '../apiAdapter'; type SupabaseInvokeResult = Awaited>; type FetchResult = Pick; describe('apiAdapter - Go Gateway Strangler Pattern', () => { beforeEach(() => { vi.clearAllMocks(); setUseGoGateway('verify-receipt', false); // Reset switch setGoGatewayUrl('http://localhost:8080'); // Reset gateway url }); it('routes to Supabase Deno Edge Function when USE_GO_GATEWAY is false', async () => { // 1. Mock Deno successful response vi.mocked(supabase.functions.invoke).mockResolvedValue({ data: { success: true, co2Reduction: 21, confidence: 0.95, }, error: null, } as SupabaseInvokeResult); // 2. Act const res = await invokeEdgeFunction<{ text: string }, { success: boolean; co2Reduction: number }>('verify-receipt', { text: 'receipt-data', }); // 3. Assertions expect(supabase.functions.invoke).toHaveBeenCalledWith('verify-receipt', { body: { text: 'receipt-data' }, }); expect(mockFetch).not.toHaveBeenCalled(); expect(res.success).toBe(true); expect(res.co2Reduction).toBe(21); }); it('routes to Go Gateway via fetch when USE_GO_GATEWAY is true', async () => { // 1. Enable Go Gateway routing switch setUseGoGateway('verify-receipt', true); setGoGatewayUrl('https://gateway.carboany.com'); // 2. Mock global fetch to return successful response mockFetch.mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({ data: { status: 'accepted', co2Reduction: 33, confidence: 0.88, }, meta: { traceId: 'e8838d21-f222-4777-a999-2999cb222ccc', source: 'go-gateway', audit: { ledgerTxId: null, integrity: true, }, }, error: null, }), } as FetchResult); // 3. Act const res = await invokeEdgeFunction<{ text: string }, { status: string; co2Reduction: number }>('verify-receipt', { text: 'receipt-data', }); // 4. Assertions expect(supabase.functions.invoke).not.toHaveBeenCalled(); expect(mockFetch).toHaveBeenCalledWith('https://gateway.carboany.com/api/v1/verify-receipt', expect.objectContaining({ method: 'POST', body: JSON.stringify({ text: 'receipt-data' }), })); expect(res.status).toBe('accepted'); expect(res.co2Reduction).toBe(33); }); it('routes receipt verification to the OpenAPI submission verification path when submissionId is present', async () => { setUseGoGateway('verify-receipt', true); setGoGatewayUrl('https://gateway.carboany.com'); mockFetch.mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({ data: { status: 'verified', co2Reduction: 11, }, meta: { traceId: 'e8838d21-f222-4777-a999-2999cb222ccc', source: 'go-gateway', audit: { ledgerTxId: null, integrity: true, }, }, error: null, }), } as FetchResult); const res = await invokeEdgeFunction<{ submissionId: string }, { status: string; co2Reduction: number }>('verify-receipt', { submissionId: '4d7f90b8-a52c-4a3f-84b2-986d8bbf1070', }); expect(mockFetch).toHaveBeenCalledWith( 'https://gateway.carboany.com/api/v1/submissions/4d7f90b8-a52c-4a3f-84b2-986d8bbf1070/verify', expect.objectContaining({ method: 'POST', body: JSON.stringify({ submissionId: '4d7f90b8-a52c-4a3f-84b2-986d8bbf1070' }), }) ); expect(res.status).toBe('verified'); expect(res.co2Reduction).toBe(11); }); it('throws structured exception when response data contract is broken', async () => { // Enable Go Gateway routing switch setUseGoGateway('verify-receipt', true); // Mock global fetch to return broken schema (missing "co2Reduction" which is required) mockFetch.mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({ data: { status: 'accepted', // missing co2Reduction confidence: 0.99, }, meta: { traceId: 'e8838d21-f222-4777-a999-2999cb222ccc', source: 'go-gateway', audit: { ledgerTxId: null, integrity: true, }, }, error: null, }), } as FetchResult); // Act & Assert (expecting Contract Validation error) await expect( invokeEdgeFunction<{ text: string }, { success: boolean; co2Reduction: number }>('verify-receipt', { text: 'receipt-data', }) ).rejects.toThrow( '[apiAdapter] API 계약 검증 실패: verify-receipt 응답에 필수 필드(co2Reduction)가 누락되었습니다.' ); }); });