@@ -4,73 +4,67 @@ import { describe, it } from 'node:test';
4
4
import { type FetchProxyOptions , createFetchProxy } from './fetch-proxy.ts' ;
5
5
6
6
async function testProxy (
7
- input : URL | RequestInfo ,
8
- init : RequestInit | undefined ,
7
+ request : Request ,
9
8
target : string | URL ,
10
9
options ?: FetchProxyOptions ,
11
10
) : Promise < { request : Request ; response : Response } > {
12
- let request : Request ;
11
+ let capturedRequest : Request ;
13
12
let proxy = createFetchProxy ( target , {
14
13
...options ,
15
14
fetch ( input , init ) {
16
- request = new Request ( input , init ) ;
15
+ capturedRequest = new Request ( input , init ) ;
17
16
return options ?. fetch ?.( input , init ) ?? Promise . resolve ( new Response ( ) ) ;
18
17
} ,
19
18
} ) ;
20
19
21
- let response = await proxy ( input , init ) ;
20
+ let response = await proxy ( request ) ;
22
21
23
- assert . ok ( request ! ) ;
22
+ assert . ok ( capturedRequest ! ) ;
24
23
25
- return { request, response } ;
24
+ return { request : capturedRequest , response } ;
26
25
}
27
26
28
27
describe ( 'fetch proxy' , ( ) => {
29
28
it ( 'appends the request URL pathname + search to the target URL' , async ( ) => {
30
29
let { request : request1 } = await testProxy (
31
- 'http://shopify.com' ,
32
- undefined ,
33
- 'https://remix.run:3000/rsc' ,
30
+ new Request ( 'http://shopify.com' ) ,
31
+ 'https://remix.run:3000/dest' ,
34
32
) ;
35
33
36
- assert . equal ( request1 . url , 'https://remix.run:3000/rsc ' ) ;
34
+ assert . equal ( request1 . url , 'https://remix.run:3000/dest ' ) ;
37
35
38
36
let { request : request2 } = await testProxy (
39
- 'http://shopify.com/?q=remix' ,
40
- undefined ,
41
- 'https://remix.run:3000/rsc' ,
37
+ new Request ( 'http://shopify.com/?q=remix' ) ,
38
+ 'https://remix.run:3000/dest' ,
42
39
) ;
43
40
44
- assert . equal ( request2 . url , 'https://remix.run:3000/rsc ?q=remix' ) ;
41
+ assert . equal ( request2 . url , 'https://remix.run:3000/dest ?q=remix' ) ;
45
42
46
43
let { request : request3 } = await testProxy (
47
- 'http://shopify.com/search?q=remix' ,
48
- undefined ,
44
+ new Request ( 'http://shopify.com/search?q=remix' ) ,
49
45
'https://remix.run:3000/' ,
50
46
) ;
51
47
52
48
assert . equal ( request3 . url , 'https://remix.run:3000/search?q=remix' ) ;
53
49
54
50
let { request : request4 } = await testProxy (
55
- 'http://shopify.com/search?q=remix' ,
56
- undefined ,
57
- 'https://remix.run:3000/rsc' ,
51
+ new Request ( 'http://shopify.com/search?q=remix' ) ,
52
+ 'https://remix.run:3000/dest' ,
58
53
) ;
59
54
60
- assert . equal ( request4 . url , 'https://remix.run:3000/rsc /search?q=remix' ) ;
55
+ assert . equal ( request4 . url , 'https://remix.run:3000/dest /search?q=remix' ) ;
61
56
} ) ;
62
57
63
58
it ( 'forwards request method, headers, and body' , async ( ) => {
64
59
let { request } = await testProxy (
65
- 'http://shopify.com/search?q=remix' ,
66
- {
60
+ new Request ( 'http://shopify.com/search?q=remix' , {
67
61
method : 'POST' ,
68
62
headers : {
69
63
'Content-Type' : 'text/plain' ,
70
64
} ,
71
65
body : 'hello' ,
72
- } ,
73
- 'https://remix.run:3000/rsc ' ,
66
+ } ) ,
67
+ 'https://remix.run:3000/dest ' ,
74
68
) ;
75
69
76
70
assert . equal ( request . method , 'POST' ) ;
@@ -80,23 +74,36 @@ describe('fetch proxy', () => {
80
74
81
75
it ( 'forwards an empty request body' , async ( ) => {
82
76
let { request } = await testProxy (
83
- 'http://shopify.com/search?q=remix' ,
84
- {
77
+ new Request ( 'http://shopify.com/search?q=remix' , {
85
78
method : 'POST' ,
86
- } ,
87
- 'https://remix.run:3000/rsc ' ,
79
+ } ) ,
80
+ 'https://remix.run:3000/dest ' ,
88
81
) ;
89
82
90
83
assert . equal ( request . method , 'POST' ) ;
91
84
assert . equal ( request . headers . get ( 'Content-Type' ) , null ) ;
92
85
assert . equal ( await request . text ( ) , '' ) ;
93
86
} ) ;
94
87
88
+ it ( 'forwards various HTTP methods correctly' , async ( ) => {
89
+ let methods = [ 'GET' , 'POST' , 'PUT' , 'DELETE' , 'PATCH' , 'OPTIONS' , 'HEAD' ] ;
90
+
91
+ for ( let method of methods ) {
92
+ let { request } = await testProxy (
93
+ new Request ( 'http://shopify.com/api/resource' , {
94
+ method,
95
+ } ) ,
96
+ 'https://remix.run:3000/backend' ,
97
+ ) ;
98
+
99
+ assert . equal ( request . method , method , `Method ${ method } should be forwarded correctly` ) ;
100
+ }
101
+ } ) ;
102
+
95
103
it ( 'does not append X-Forwarded-Proto and X-Forwarded-Host headers by default' , async ( ) => {
96
104
let { request } = await testProxy (
97
- 'http://shopify.com:8080/search?q=remix' ,
98
- undefined ,
99
- 'https://remix.run:3000/rsc' ,
105
+ new Request ( 'http://shopify.com:8080/search?q=remix' ) ,
106
+ 'https://remix.run:3000/dest' ,
100
107
) ;
101
108
102
109
assert . equal ( request . headers . get ( 'X-Forwarded-Proto' ) , null ) ;
@@ -105,9 +112,8 @@ describe('fetch proxy', () => {
105
112
106
113
it ( 'appends X-Forwarded-Proto and X-Forwarded-Host headers when desired' , async ( ) => {
107
114
let { request } = await testProxy (
108
- 'http://shopify.com:8080/search?q=remix' ,
109
- undefined ,
110
- 'https://remix.run:3000/rsc' ,
115
+ new Request ( 'http://shopify.com:8080/search?q=remix' ) ,
116
+ 'https://remix.run:3000/dest' ,
111
117
{
112
118
xForwardedHeaders : true ,
113
119
} ,
@@ -119,31 +125,31 @@ describe('fetch proxy', () => {
119
125
120
126
it ( 'forwards additional request init options' , async ( ) => {
121
127
let { request } = await testProxy (
122
- 'http://shopify.com/search?q=remix' ,
123
- {
128
+ new Request ( 'http://shopify.com/search?q=remix' , {
129
+ method : 'DELETE' ,
124
130
cache : 'no-cache' ,
125
131
credentials : 'include' ,
126
132
redirect : 'manual' ,
127
- } ,
128
- 'https://remix.run:3000/rsc ' ,
133
+ } ) ,
134
+ 'https://remix.run:3000/dest ' ,
129
135
) ;
130
136
137
+ assert . equal ( request . method , 'DELETE' ) ;
131
138
assert . equal ( request . cache , 'no-cache' ) ;
132
139
assert . equal ( request . credentials , 'include' ) ;
133
140
assert . equal ( request . redirect , 'manual' ) ;
134
141
} ) ;
135
142
136
143
it ( 'rewrites cookie domain and path' , async ( ) => {
137
144
let { response } = await testProxy (
138
- 'http://shopify.com/search?q=remix' ,
139
- undefined ,
140
- 'https://remix.run:3000/rsc' ,
145
+ new Request ( 'http://shopify.com/search?q=remix' ) ,
146
+ 'https://remix.run:3000/dest' ,
141
147
{
142
148
async fetch ( ) {
143
149
return new Response ( null , {
144
150
headers : [
145
- [ 'Set-Cookie' , 'name=value; Domain=remix.run:3000; Path=/rsc /search' ] ,
146
- [ 'Set-Cookie' , 'name2=value2; Domain=remix.run:3000; Path=/rsc ' ] ,
151
+ [ 'Set-Cookie' , 'name=value; Domain=remix.run:3000; Path=/dest /search' ] ,
152
+ [ 'Set-Cookie' , 'name2=value2; Domain=remix.run:3000; Path=/dest ' ] ,
147
153
] ,
148
154
} ) ;
149
155
} ,
@@ -159,17 +165,16 @@ describe('fetch proxy', () => {
159
165
160
166
it ( 'does not rewrite cookie domain and path when opting-out' , async ( ) => {
161
167
let { response } = await testProxy (
162
- 'http://shopify.com/?q=remix' ,
163
- undefined ,
164
- 'https://remix.run:3000/rsc' ,
168
+ new Request ( 'http://shopify.com/?q=remix' ) ,
169
+ 'https://remix.run:3000/dest' ,
165
170
{
166
171
rewriteCookieDomain : false ,
167
172
rewriteCookiePath : false ,
168
173
async fetch ( ) {
169
174
return new Response ( null , {
170
175
headers : [
171
- [ 'Set-Cookie' , 'name=value; Domain=remix.run:3000; Path=/rsc /search' ] ,
172
- [ 'Set-Cookie' , 'name2=value2; Domain=remix.run:3000; Path=/rsc ' ] ,
176
+ [ 'Set-Cookie' , 'name=value; Domain=remix.run:3000; Path=/dest /search' ] ,
177
+ [ 'Set-Cookie' , 'name2=value2; Domain=remix.run:3000; Path=/dest ' ] ,
173
178
] ,
174
179
} ) ;
175
180
} ,
@@ -179,7 +184,157 @@ describe('fetch proxy', () => {
179
184
let setCookie = response . headers . getSetCookie ( ) ;
180
185
assert . ok ( setCookie ) ;
181
186
assert . equal ( setCookie . length , 2 ) ;
182
- assert . equal ( setCookie [ 0 ] , 'name=value; Domain=remix.run:3000; Path=/rsc/search' ) ;
183
- assert . equal ( setCookie [ 1 ] , 'name2=value2; Domain=remix.run:3000; Path=/rsc' ) ;
187
+ assert . equal ( setCookie [ 0 ] , 'name=value; Domain=remix.run:3000; Path=/dest/search' ) ;
188
+ assert . equal ( setCookie [ 1 ] , 'name2=value2; Domain=remix.run:3000; Path=/dest' ) ;
189
+ } ) ;
190
+
191
+ it ( 'preserves all request properties when using proxy(request)' , async ( ) => {
192
+ let capturedRequest : Request ;
193
+ let proxy = createFetchProxy ( 'https://remix.run:3000/dest' , {
194
+ fetch ( input , init ) {
195
+ capturedRequest = new Request ( input , init ) ;
196
+ return Promise . resolve ( new Response ( ) ) ;
197
+ } ,
198
+ } ) ;
199
+
200
+ let originalRequest = new Request ( 'http://shopify.com/api/resource' , {
201
+ method : 'PUT' ,
202
+ cache : 'no-store' ,
203
+ credentials : 'omit' ,
204
+ integrity : 'sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=' ,
205
+ keepalive : true ,
206
+ mode : 'cors' ,
207
+ redirect : 'error' ,
208
+ referrer : 'http://example.com' ,
209
+ referrerPolicy : 'no-referrer' ,
210
+ } ) ;
211
+
212
+ await proxy ( originalRequest ) ;
213
+
214
+ assert . ok ( capturedRequest ! ) ;
215
+ assert . equal ( capturedRequest . method , 'PUT' ) ;
216
+ assert . equal ( capturedRequest . cache , 'no-store' ) ;
217
+ assert . equal ( capturedRequest . credentials , 'omit' ) ;
218
+ assert . equal ( capturedRequest . integrity , 'sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=' ) ;
219
+ assert . equal ( capturedRequest . keepalive , true ) ;
220
+ assert . equal ( capturedRequest . mode , 'cors' ) ;
221
+ assert . equal ( capturedRequest . redirect , 'error' ) ;
222
+ assert . equal ( capturedRequest . referrer , 'http://example.com/' ) ;
223
+ assert . equal ( capturedRequest . referrerPolicy , 'no-referrer' ) ;
224
+ } ) ;
225
+
226
+ it ( 'allows init to override request properties' , async ( ) => {
227
+ let capturedRequest : Request ;
228
+ let proxy = createFetchProxy ( 'https://remix.run:3000/dest' , {
229
+ fetch ( input , init ) {
230
+ capturedRequest = new Request ( input , init ) ;
231
+ return Promise . resolve ( new Response ( ) ) ;
232
+ } ,
233
+ } ) ;
234
+
235
+ let originalRequest = new Request ( 'http://shopify.com/api/resource' , {
236
+ method : 'PUT' ,
237
+ cache : 'no-store' ,
238
+ credentials : 'omit' ,
239
+ } ) ;
240
+
241
+ await proxy ( originalRequest , {
242
+ method : 'POST' ,
243
+ cache : 'default' ,
244
+ credentials : 'include' ,
245
+ } ) ;
246
+
247
+ assert . ok ( capturedRequest ! ) ;
248
+ assert . equal ( capturedRequest . method , 'POST' ) ;
249
+ assert . equal ( capturedRequest . cache , 'default' ) ;
250
+ assert . equal ( capturedRequest . credentials , 'include' ) ;
251
+ } ) ;
252
+ } ) ;
253
+
254
+ describe ( 'fetch proxy (double-arg style)' , ( ) => {
255
+ it ( 'works with proxy(url, init) style' , async ( ) => {
256
+ let capturedRequest : Request ;
257
+ let proxy = createFetchProxy ( 'https://remix.run:3000/dest' , {
258
+ fetch ( input , init ) {
259
+ capturedRequest = new Request ( input , init ) ;
260
+ return Promise . resolve ( new Response ( ) ) ;
261
+ } ,
262
+ } ) ;
263
+
264
+ await proxy ( 'http://shopify.com/api/resource' , {
265
+ method : 'PATCH' ,
266
+ cache : 'reload' ,
267
+ credentials : 'same-origin' ,
268
+ headers : {
269
+ 'X-Custom' : 'value' ,
270
+ } ,
271
+ } ) ;
272
+
273
+ assert . ok ( capturedRequest ! ) ;
274
+ assert . equal ( capturedRequest . method , 'PATCH' ) ;
275
+ assert . equal ( capturedRequest . cache , 'reload' ) ;
276
+ assert . equal ( capturedRequest . credentials , 'same-origin' ) ;
277
+ assert . equal ( capturedRequest . headers . get ( 'X-Custom' ) , 'value' ) ;
278
+ assert . equal ( capturedRequest . url , 'https://remix.run:3000/dest/api/resource' ) ;
279
+ } ) ;
280
+
281
+ it ( 'handles proxy(url) with defaults' , async ( ) => {
282
+ let capturedRequest : Request ;
283
+ let proxy = createFetchProxy ( 'https://remix.run:3000/dest' , {
284
+ fetch ( input , init ) {
285
+ capturedRequest = new Request ( input , init ) ;
286
+ return Promise . resolve ( new Response ( ) ) ;
287
+ } ,
288
+ } ) ;
289
+
290
+ await proxy ( 'http://shopify.com/api/resource' ) ;
291
+
292
+ assert . ok ( capturedRequest ! ) ;
293
+ assert . equal ( capturedRequest . method , 'GET' ) ;
294
+ assert . equal ( capturedRequest . url , 'https://remix.run:3000/dest/api/resource' ) ;
295
+ } ) ;
296
+
297
+ it ( 'forwards headers correctly with proxy(url, init)' , async ( ) => {
298
+ let capturedRequest : Request ;
299
+ let proxy = createFetchProxy ( 'https://remix.run:3000/dest' , {
300
+ fetch ( input , init ) {
301
+ capturedRequest = new Request ( input , init ) ;
302
+ return Promise . resolve ( new Response ( ) ) ;
303
+ } ,
304
+ } ) ;
305
+
306
+ await proxy ( 'http://shopify.com/api/resource' , {
307
+ headers : {
308
+ Authorization : 'Bearer token123' ,
309
+ 'Content-Type' : 'application/json' ,
310
+ } ,
311
+ } ) ;
312
+
313
+ assert . ok ( capturedRequest ! ) ;
314
+ assert . equal ( capturedRequest . headers . get ( 'Authorization' ) , 'Bearer token123' ) ;
315
+ assert . equal ( capturedRequest . headers . get ( 'Content-Type' ) , 'application/json' ) ;
316
+ } ) ;
317
+
318
+ it ( 'handles body with proxy(url, init)' , async ( ) => {
319
+ let capturedRequest : Request ;
320
+ let proxy = createFetchProxy ( 'https://remix.run:3000/dest' , {
321
+ fetch ( input , init ) {
322
+ capturedRequest = new Request ( input , init ) ;
323
+ return Promise . resolve ( new Response ( ) ) ;
324
+ } ,
325
+ } ) ;
326
+
327
+ let body = JSON . stringify ( { name : 'test' , value : 123 } ) ;
328
+ await proxy ( 'http://shopify.com/api/resource' , {
329
+ method : 'POST' ,
330
+ headers : {
331
+ 'Content-Type' : 'application/json' ,
332
+ } ,
333
+ body,
334
+ } ) ;
335
+
336
+ assert . ok ( capturedRequest ! ) ;
337
+ assert . equal ( capturedRequest . method , 'POST' ) ;
338
+ assert . equal ( await capturedRequest . text ( ) , body ) ;
184
339
} ) ;
185
340
} ) ;
0 commit comments