File size: 5,292 Bytes
5c98911
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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<ReturnType<typeof supabase.functions.invoke>>;
type FetchResult = Pick<Response, 'ok' | 'json' | 'text'>;

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)κ°€ λˆ„λ½λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'
    );
  });
});