-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathtunnel.test.ts
More file actions
136 lines (110 loc) · 4.79 KB
/
tunnel.test.ts
File metadata and controls
136 lines (110 loc) · 4.79 KB
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
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { getEnvelopeEndpointWithUrlEncodedAuth } from '../../../src/api';
import { makeDsn } from '../../../src/utils/dsn';
import { createEnvelope, serializeEnvelope } from '../../../src/utils/envelope';
import { handleTunnelRequest } from '../../../src/utils/tunnel';
const TEST_DSN = 'https://public@dsn.ingest.sentry.io/1337';
function makeEnvelopeRequest(envelopeHeader: Record<string, unknown>): Request {
const envelope = createEnvelope(envelopeHeader, []);
const body = serializeEnvelope(envelope);
return new Request('http://localhost/tunnel', { method: 'POST', body });
}
describe('handleTunnelRequest', () => {
let fetchMock: ReturnType<typeof vi.fn>;
beforeEach(() => {
fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('forwards the envelope to Sentry and returns the upstream response', async () => {
const upstreamResponse = new Response('ok', { status: 200 });
fetchMock.mockResolvedValueOnce(upstreamResponse);
const result = await handleTunnelRequest({
request: makeEnvelopeRequest({ dsn: TEST_DSN }),
allowedDsns: [TEST_DSN],
});
expect(fetchMock).toHaveBeenCalledOnce();
const [url, init] = fetchMock.mock.calls[0]!;
expect(url).toBe(getEnvelopeEndpointWithUrlEncodedAuth(makeDsn(TEST_DSN)!));
expect(init.method).toBe('POST');
expect(init.headers).toEqual({ 'Content-Type': 'application/x-sentry-envelope' });
expect(init.body).toBeInstanceOf(Uint8Array);
expect(result).toBe(upstreamResponse);
});
it('returns 500 when allowedDsns is empty', async () => {
const result = await handleTunnelRequest({
request: makeEnvelopeRequest({ dsn: TEST_DSN }),
allowedDsns: [],
});
expect(result).toBeInstanceOf(Response);
expect(result.status).toBe(500);
expect(await result.text()).toBe('Tunnel not configured');
expect(fetchMock).not.toHaveBeenCalled();
});
it('returns 400 when the envelope has no DSN in the header', async () => {
const result = await handleTunnelRequest({
request: makeEnvelopeRequest({}),
allowedDsns: [TEST_DSN],
});
expect(result).toBeInstanceOf(Response);
expect(result.status).toBe(400);
expect(await result.text()).toBe('Invalid envelope: missing DSN');
expect(fetchMock).not.toHaveBeenCalled();
});
it('returns 400 when the envelope body contains malformed JSON', async () => {
const result = await handleTunnelRequest({
request: new Request('http://localhost/tunnel', { method: 'POST', body: 'not valid envelope data{{{' }),
allowedDsns: [TEST_DSN],
});
expect(result).toBeInstanceOf(Response);
expect(result.status).toBe(400);
expect(await result.text()).toBe('Invalid envelope');
expect(fetchMock).not.toHaveBeenCalled();
});
it('returns 403 when the envelope DSN is not in allowedDsns', async () => {
const result = await handleTunnelRequest({
request: makeEnvelopeRequest({ dsn: 'https://other@example.com/9999' }),
allowedDsns: [TEST_DSN],
});
expect(result).toBeInstanceOf(Response);
expect(result.status).toBe(403);
expect(await result.text()).toBe('DSN not allowed');
expect(fetchMock).not.toHaveBeenCalled();
});
it('returns 403 when the DSN string cannot be parsed into components', async () => {
const malformedDsn = 'not-a-valid-dsn';
const result = await handleTunnelRequest({
request: makeEnvelopeRequest({ dsn: malformedDsn }),
allowedDsns: [malformedDsn],
});
expect(result).toBeInstanceOf(Response);
expect(result.status).toBe(403);
expect(await result.text()).toBe('Invalid DSN');
expect(fetchMock).not.toHaveBeenCalled();
});
it('forwards the envelope when multiple DSNs are configured', async () => {
const otherDsn = 'https://other@example.com/9999';
const upstreamResponse = new Response('ok', { status: 200 });
fetchMock.mockResolvedValueOnce(upstreamResponse);
const result = await handleTunnelRequest({
request: makeEnvelopeRequest({ dsn: TEST_DSN }),
allowedDsns: [otherDsn, TEST_DSN],
});
expect(fetchMock).toHaveBeenCalledOnce();
const [url] = fetchMock.mock.calls[0]!;
expect(url).toBe(getEnvelopeEndpointWithUrlEncodedAuth(makeDsn(TEST_DSN)!));
expect(result).toBe(upstreamResponse);
});
it('returns 500 when fetch throws a network error', async () => {
fetchMock.mockRejectedValueOnce(new Error('Network failure'));
const result = await handleTunnelRequest({
request: makeEnvelopeRequest({ dsn: TEST_DSN }),
allowedDsns: [TEST_DSN],
});
expect(result).toBeInstanceOf(Response);
expect(result.status).toBe(500);
expect(await result.text()).toBe('Failed to forward envelope to Sentry');
});
});