@fastify/compress 9.1.1 → 9.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,741 @@
1
+ 'use strict'
2
+
3
+ const { describe, test } = require('node:test')
4
+ const { createReadStream } = require('node:fs')
5
+ const { randomBytes } = require('node:crypto')
6
+ const zlib = require('node:zlib')
7
+ const Fastify = require('fastify')
8
+ const compressPlugin = require('../index')
9
+
10
+ // A compressible body of a given size, so tests can pick which side of
11
+ // `threshold` / `syncThreshold` a payload falls on
12
+ function buildPayload (size) {
13
+ return 'the quick brown fox jumps over the lazy dog '.repeat(Math.ceil(size / 44)).slice(0, size)
14
+ }
15
+
16
+ // The default `syncThreshold` is derived from the host parallelism, so every test that
17
+ // cares about which path a payload takes pins it explicitly to stay deterministic
18
+ const syncThreshold = 32768
19
+
20
+ const smallPayload = buildPayload(4096)
21
+ const largePayload = buildPayload(200 * 1024)
22
+ // incompressible, so that the already compressed payloads built from it stay
23
+ // comfortably above `threshold` and below the pinned `syncThreshold`
24
+ const incompressiblePayload = randomBytes(4096)
25
+
26
+ describe('When a buffered payload fits within `syncThreshold`, it should be compressed synchronously :', async () => {
27
+ test('using gzip', async (t) => {
28
+ t.plan(4)
29
+
30
+ const fastify = Fastify()
31
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
32
+
33
+ fastify.get('/', (_request, reply) => {
34
+ reply.type('text/plain').send(smallPayload)
35
+ })
36
+
37
+ const response = await fastify.inject({
38
+ url: '/',
39
+ method: 'GET',
40
+ headers: { 'accept-encoding': 'gzip' }
41
+ })
42
+ t.assert.equal(response.headers.vary, 'accept-encoding')
43
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
44
+ t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString())
45
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
46
+ })
47
+
48
+ test('using deflate', async (t) => {
49
+ t.plan(4)
50
+
51
+ const fastify = Fastify()
52
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
53
+
54
+ fastify.get('/', (_request, reply) => {
55
+ reply.type('text/plain').send(smallPayload)
56
+ })
57
+
58
+ const response = await fastify.inject({
59
+ url: '/',
60
+ method: 'GET',
61
+ headers: { 'accept-encoding': 'deflate' }
62
+ })
63
+ t.assert.equal(response.headers.vary, 'accept-encoding')
64
+ t.assert.equal(response.headers['content-encoding'], 'deflate')
65
+ t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString())
66
+ t.assert.equal(zlib.inflateSync(response.rawPayload).toString('utf-8'), smallPayload)
67
+ })
68
+
69
+ test('using brotli', async (t) => {
70
+ t.plan(4)
71
+
72
+ const fastify = Fastify()
73
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
74
+
75
+ fastify.get('/', (_request, reply) => {
76
+ reply.type('text/plain').send(smallPayload)
77
+ })
78
+
79
+ const response = await fastify.inject({
80
+ url: '/',
81
+ method: 'GET',
82
+ headers: { 'accept-encoding': 'br' }
83
+ })
84
+ t.assert.equal(response.headers.vary, 'accept-encoding')
85
+ t.assert.equal(response.headers['content-encoding'], 'br')
86
+ t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString())
87
+ t.assert.equal(zlib.brotliDecompressSync(response.rawPayload).toString('utf-8'), smallPayload)
88
+ })
89
+
90
+ test('using zstd', async (t) => {
91
+ if (typeof zlib.createZstdCompress !== 'function') {
92
+ t.skip('zstd not supported in this Node.js version')
93
+ return
94
+ }
95
+ t.plan(4)
96
+
97
+ const fastify = Fastify()
98
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
99
+
100
+ fastify.get('/', (_request, reply) => {
101
+ reply.type('text/plain').send(smallPayload)
102
+ })
103
+
104
+ const response = await fastify.inject({
105
+ url: '/',
106
+ method: 'GET',
107
+ headers: { 'accept-encoding': 'zstd' }
108
+ })
109
+ t.assert.equal(response.headers.vary, 'accept-encoding')
110
+ t.assert.equal(response.headers['content-encoding'], 'zstd')
111
+ t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString())
112
+ t.assert.equal(zlib.zstdDecompressSync(response.rawPayload).toString('utf-8'), smallPayload)
113
+ })
114
+
115
+ test('using a Buffer payload', async (t) => {
116
+ t.plan(3)
117
+
118
+ const fastify = Fastify()
119
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
120
+
121
+ const buf = Buffer.from(smallPayload)
122
+ fastify.get('/', (_request, reply) => {
123
+ reply.type('text/plain').send(buf)
124
+ })
125
+
126
+ const response = await fastify.inject({
127
+ url: '/',
128
+ method: 'GET',
129
+ headers: { 'accept-encoding': 'gzip' }
130
+ })
131
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
132
+ t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString())
133
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
134
+ })
135
+
136
+ test('using `reply.compress()`', async (t) => {
137
+ t.plan(4)
138
+
139
+ const fastify = Fastify()
140
+ await fastify.register(compressPlugin, { global: false, syncThreshold })
141
+
142
+ fastify.get('/', (_request, reply) => {
143
+ reply.type('text/plain').compress(smallPayload)
144
+ })
145
+
146
+ const response = await fastify.inject({
147
+ url: '/',
148
+ method: 'GET',
149
+ headers: { 'accept-encoding': 'gzip' }
150
+ })
151
+ t.assert.equal(response.headers.vary, 'accept-encoding')
152
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
153
+ t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString())
154
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
155
+ })
156
+
157
+ test('using a route level `compress` configuration', async (t) => {
158
+ t.plan(3)
159
+
160
+ const fastify = Fastify()
161
+ await fastify.register(compressPlugin, { global: false, syncThreshold })
162
+
163
+ fastify.get('/', {
164
+ compress: { syncThreshold: 8192 }
165
+ }, (_request, reply) => {
166
+ reply.type('text/plain').send(smallPayload)
167
+ })
168
+
169
+ const response = await fastify.inject({
170
+ url: '/',
171
+ method: 'GET',
172
+ headers: { 'accept-encoding': 'gzip' }
173
+ })
174
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
175
+ t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString())
176
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
177
+ })
178
+
179
+ test('it should honour `zlibOptions`', async (t) => {
180
+ t.plan(2)
181
+
182
+ const fastify = Fastify()
183
+ await fastify.register(compressPlugin, { global: true, syncThreshold, zlibOptions: { level: 9 } })
184
+
185
+ fastify.get('/', (_request, reply) => {
186
+ reply.type('text/plain').send(smallPayload)
187
+ })
188
+
189
+ const response = await fastify.inject({
190
+ url: '/',
191
+ method: 'GET',
192
+ headers: { 'accept-encoding': 'gzip' }
193
+ })
194
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
195
+ t.assert.equal(response.rawPayload.length, zlib.gzipSync(smallPayload, { level: 9 }).length)
196
+ })
197
+
198
+ test('it should honour `brotliOptions`', async (t) => {
199
+ t.plan(2)
200
+
201
+ const brotliOptions = { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 } }
202
+
203
+ const fastify = Fastify()
204
+ await fastify.register(compressPlugin, { global: true, syncThreshold, brotliOptions })
205
+
206
+ fastify.get('/', (_request, reply) => {
207
+ reply.type('text/plain').send(smallPayload)
208
+ })
209
+
210
+ const response = await fastify.inject({
211
+ url: '/',
212
+ method: 'GET',
213
+ headers: { 'accept-encoding': 'br' }
214
+ })
215
+ t.assert.equal(zlib.brotliDecompressSync(response.rawPayload).toString('utf-8'), smallPayload)
216
+ t.assert.equal(response.rawPayload.length, zlib.brotliCompressSync(smallPayload, brotliOptions).length)
217
+ })
218
+ })
219
+
220
+ describe('An explicit `syncThreshold` should always win over the computed default :', async () => {
221
+ // the computed default is clamped to [4096, 65536], so these two values sit on either
222
+ // side of every default the formula can produce, whatever the host reports
223
+ test('a value below the computed default should push a payload onto the stream pipeline', async (t) => {
224
+ t.plan(3)
225
+
226
+ const fastify = Fastify()
227
+ await fastify.register(compressPlugin, { global: true, syncThreshold: 2048 })
228
+
229
+ fastify.get('/', (_request, reply) => {
230
+ reply.type('text/plain').send(smallPayload)
231
+ })
232
+
233
+ const response = await fastify.inject({
234
+ url: '/',
235
+ method: 'GET',
236
+ headers: { 'accept-encoding': 'gzip' }
237
+ })
238
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
239
+ t.assert.ok(!response.headers['content-length'], 'no content length')
240
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
241
+ })
242
+
243
+ test('a value above the computed default should keep a payload on the synchronous path', async (t) => {
244
+ t.plan(3)
245
+
246
+ const hugePayload = buildPayload(128 * 1024)
247
+
248
+ const fastify = Fastify()
249
+ await fastify.register(compressPlugin, { global: true, syncThreshold: 256 * 1024 })
250
+
251
+ fastify.get('/', (_request, reply) => {
252
+ reply.type('text/plain').send(hugePayload)
253
+ })
254
+
255
+ const response = await fastify.inject({
256
+ url: '/',
257
+ method: 'GET',
258
+ headers: { 'accept-encoding': 'gzip' }
259
+ })
260
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
261
+ t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString())
262
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), hugePayload)
263
+ })
264
+
265
+ test('the computed default should compress a payload it covers on every host', async (t) => {
266
+ t.plan(3)
267
+
268
+ // 2 KiB is above `threshold` and below the lowest default the formula can produce
269
+ const payload = buildPayload(2048)
270
+
271
+ const fastify = Fastify()
272
+ await fastify.register(compressPlugin, { global: true })
273
+
274
+ fastify.get('/', (_request, reply) => {
275
+ reply.type('text/plain').send(payload)
276
+ })
277
+
278
+ const response = await fastify.inject({
279
+ url: '/',
280
+ method: 'GET',
281
+ headers: { 'accept-encoding': 'gzip' }
282
+ })
283
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
284
+ t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString())
285
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), payload)
286
+ })
287
+
288
+ test('the computed default should stream a payload above the highest value it can produce', async (t) => {
289
+ t.plan(3)
290
+
291
+ // 256 KiB is above the upper clamp of the formula, so it streams on every host
292
+ const payload = buildPayload(256 * 1024)
293
+
294
+ const fastify = Fastify()
295
+ await fastify.register(compressPlugin, { global: true })
296
+
297
+ fastify.get('/', (_request, reply) => {
298
+ reply.type('text/plain').send(payload)
299
+ })
300
+
301
+ const response = await fastify.inject({
302
+ url: '/',
303
+ method: 'GET',
304
+ headers: { 'accept-encoding': 'gzip' }
305
+ })
306
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
307
+ t.assert.ok(!response.headers['content-length'], 'no content length')
308
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), payload)
309
+ })
310
+ })
311
+
312
+ describe('It should fall back to the stream pipeline when the payload is not eligible :', async () => {
313
+ test('when the payload is smaller than `threshold`', async (t) => {
314
+ t.plan(3)
315
+
316
+ const fastify = Fastify()
317
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
318
+
319
+ fastify.get('/', (_request, reply) => {
320
+ reply.type('text/plain').send('hello world')
321
+ })
322
+
323
+ const response = await fastify.inject({
324
+ url: '/',
325
+ method: 'GET',
326
+ headers: { 'accept-encoding': 'gzip' }
327
+ })
328
+ t.assert.ok(!response.headers.vary)
329
+ t.assert.ok(!response.headers['content-encoding'])
330
+ t.assert.equal(response.payload, 'hello world')
331
+ })
332
+
333
+ test('when the payload is bigger than `syncThreshold`', async (t) => {
334
+ t.plan(4)
335
+
336
+ const fastify = Fastify()
337
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
338
+
339
+ fastify.get('/', (_request, reply) => {
340
+ reply.type('text/plain').send(largePayload)
341
+ })
342
+
343
+ const response = await fastify.inject({
344
+ url: '/',
345
+ method: 'GET',
346
+ headers: { 'accept-encoding': 'gzip' }
347
+ })
348
+ t.assert.equal(response.headers.vary, 'accept-encoding')
349
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
350
+ // the streamed path cannot know the compressed size upfront
351
+ t.assert.ok(!response.headers['content-length'], 'no content length')
352
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), largePayload)
353
+ })
354
+
355
+ test('when `syncThreshold` is `0`', async (t) => {
356
+ t.plan(4)
357
+
358
+ const fastify = Fastify()
359
+ await fastify.register(compressPlugin, { global: true, syncThreshold: 0 })
360
+
361
+ fastify.get('/', (_request, reply) => {
362
+ reply.type('text/plain').send(smallPayload)
363
+ })
364
+
365
+ const response = await fastify.inject({
366
+ url: '/',
367
+ method: 'GET',
368
+ headers: { 'accept-encoding': 'gzip' }
369
+ })
370
+ t.assert.equal(response.headers.vary, 'accept-encoding')
371
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
372
+ t.assert.ok(!response.headers['content-length'], 'no content length')
373
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
374
+ })
375
+
376
+ test('when `syncThreshold` is `0` using `reply.compress()`', async (t) => {
377
+ t.plan(3)
378
+
379
+ const fastify = Fastify()
380
+ await fastify.register(compressPlugin, { global: false, syncThreshold: 0 })
381
+
382
+ fastify.get('/', (_request, reply) => {
383
+ reply.type('text/plain').compress(smallPayload)
384
+ })
385
+
386
+ const response = await fastify.inject({
387
+ url: '/',
388
+ method: 'GET',
389
+ headers: { 'accept-encoding': 'gzip' }
390
+ })
391
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
392
+ t.assert.ok(!response.headers['content-length'], 'no content length')
393
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
394
+ })
395
+
396
+ test('when the payload is a Stream', async (t) => {
397
+ t.plan(3)
398
+
399
+ const fastify = Fastify()
400
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
401
+
402
+ fastify.get('/', (_request, reply) => {
403
+ reply.type('text/plain').send(createReadStream('./package.json'))
404
+ })
405
+
406
+ const response = await fastify.inject({
407
+ url: '/',
408
+ method: 'GET',
409
+ headers: { 'accept-encoding': 'gzip' }
410
+ })
411
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
412
+ t.assert.ok(!response.headers['content-length'], 'no content length')
413
+ t.assert.ok(zlib.gunzipSync(response.rawPayload).length > 0)
414
+ })
415
+
416
+ test('when the payload is a Web ReadableStream', async (t) => {
417
+ t.plan(3)
418
+
419
+ const fastify = Fastify()
420
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
421
+
422
+ fastify.get('/', (_request, reply) => {
423
+ const stream = new ReadableStream({
424
+ start (controller) {
425
+ controller.enqueue(new TextEncoder().encode(smallPayload))
426
+ controller.close()
427
+ }
428
+ })
429
+ reply.type('text/plain').send(stream)
430
+ })
431
+
432
+ const response = await fastify.inject({
433
+ url: '/',
434
+ method: 'GET',
435
+ headers: { 'accept-encoding': 'gzip' }
436
+ })
437
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
438
+ t.assert.ok(!response.headers['content-length'], 'no content length')
439
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
440
+ })
441
+
442
+ test('when a custom `zlib` provides no synchronous method', async (t) => {
443
+ t.plan(4)
444
+
445
+ let usedCustom = false
446
+ const customZlib = { createGzip: () => (usedCustom = true) && zlib.createGzip() }
447
+
448
+ const fastify = Fastify()
449
+ await fastify.register(compressPlugin, { global: true, syncThreshold, zlib: customZlib })
450
+
451
+ fastify.get('/', (_request, reply) => {
452
+ reply.type('text/plain').send(smallPayload)
453
+ })
454
+
455
+ const response = await fastify.inject({
456
+ url: '/',
457
+ method: 'GET',
458
+ headers: { 'accept-encoding': 'gzip' }
459
+ })
460
+ t.assert.equal(usedCustom, true)
461
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
462
+ t.assert.ok(!response.headers['content-length'], 'no content length')
463
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
464
+ })
465
+
466
+ test('when a custom `zlib` provides no synchronous method using `reply.compress()`', async (t) => {
467
+ t.plan(3)
468
+
469
+ let usedCustom = false
470
+ const customZlib = { createDeflate: () => (usedCustom = true) && zlib.createDeflate() }
471
+
472
+ const fastify = Fastify()
473
+ await fastify.register(compressPlugin, { global: false, syncThreshold, zlib: customZlib })
474
+
475
+ fastify.get('/', (_request, reply) => {
476
+ reply.type('text/plain').compress(smallPayload)
477
+ })
478
+
479
+ const response = await fastify.inject({
480
+ url: '/',
481
+ method: 'GET',
482
+ headers: { 'accept-encoding': 'deflate' }
483
+ })
484
+ t.assert.equal(usedCustom, true)
485
+ t.assert.ok(!response.headers['content-length'], 'no content length')
486
+ t.assert.equal(zlib.inflateSync(response.rawPayload).toString('utf-8'), smallPayload)
487
+ })
488
+
489
+ test('when `removeContentLengthHeader` is `false` and the reply already carries a `Content-Length`', async (t) => {
490
+ t.plan(3)
491
+
492
+ const fastify = Fastify()
493
+ await fastify.register(compressPlugin, { global: true, syncThreshold, removeContentLengthHeader: false })
494
+
495
+ fastify.get('/', (_request, reply) => {
496
+ reply
497
+ .type('text/plain')
498
+ .header('content-length', '' + Buffer.byteLength(smallPayload))
499
+ .send(smallPayload)
500
+ })
501
+
502
+ const response = await fastify.inject({
503
+ url: '/',
504
+ method: 'GET',
505
+ headers: { 'accept-encoding': 'gzip' }
506
+ })
507
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
508
+ // the caller provided value is preserved, as it is on the streamed path
509
+ t.assert.equal(response.headers['content-length'], Buffer.byteLength(smallPayload).toString())
510
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
511
+ })
512
+ })
513
+
514
+ describe('When `syncThreshold` is `0`, every encoding should keep using the stream pipeline :', async () => {
515
+ // a custom `zlib` exposing an unrelated method only: every encoding falls back
516
+ // to the native stream compressor, exactly as it did before the synchronous path
517
+ const customZlib = { createGunzip: zlib.createGunzip }
518
+
519
+ for (const [encoding, decompress] of [
520
+ ['br', zlib.brotliDecompressSync],
521
+ ['gzip', zlib.gunzipSync],
522
+ ['deflate', zlib.inflateSync]
523
+ ]) {
524
+ test(`using ${encoding} with a custom \`zlib\``, async (t) => {
525
+ t.plan(3)
526
+
527
+ const fastify = Fastify()
528
+ await fastify.register(compressPlugin, { global: true, zlib: customZlib, syncThreshold: 0 })
529
+
530
+ fastify.get('/', (_request, reply) => {
531
+ reply.type('text/plain').send(smallPayload)
532
+ })
533
+
534
+ const response = await fastify.inject({
535
+ url: '/',
536
+ method: 'GET',
537
+ headers: { 'accept-encoding': encoding }
538
+ })
539
+ t.assert.equal(response.headers['content-encoding'], encoding)
540
+ t.assert.ok(!response.headers['content-length'], 'no content length')
541
+ t.assert.equal(decompress(response.rawPayload).toString('utf-8'), smallPayload)
542
+ })
543
+ }
544
+
545
+ test('using zstd with a custom `zlib`', async (t) => {
546
+ if (typeof zlib.createZstdCompress !== 'function') {
547
+ t.skip('zstd not supported in this Node.js version')
548
+ return
549
+ }
550
+ t.plan(3)
551
+
552
+ const fastify = Fastify()
553
+ await fastify.register(compressPlugin, { global: true, zlib: customZlib, syncThreshold: 0 })
554
+
555
+ fastify.get('/', (_request, reply) => {
556
+ reply.type('text/plain').send(smallPayload)
557
+ })
558
+
559
+ const response = await fastify.inject({
560
+ url: '/',
561
+ method: 'GET',
562
+ headers: { 'accept-encoding': 'zstd' }
563
+ })
564
+ t.assert.equal(response.headers['content-encoding'], 'zstd')
565
+ t.assert.ok(!response.headers['content-length'], 'no content length')
566
+ t.assert.equal(zlib.zstdDecompressSync(response.rawPayload).toString('utf-8'), smallPayload)
567
+ })
568
+
569
+ test('using zstd with the native `zlib`', async (t) => {
570
+ if (typeof zlib.createZstdCompress !== 'function') {
571
+ t.skip('zstd not supported in this Node.js version')
572
+ return
573
+ }
574
+ t.plan(3)
575
+
576
+ const fastify = Fastify()
577
+ await fastify.register(compressPlugin, { global: true, syncThreshold: 0 })
578
+
579
+ fastify.get('/', (_request, reply) => {
580
+ reply.type('text/plain').send(smallPayload)
581
+ })
582
+
583
+ const response = await fastify.inject({
584
+ url: '/',
585
+ method: 'GET',
586
+ headers: { 'accept-encoding': 'zstd' }
587
+ })
588
+ t.assert.equal(response.headers['content-encoding'], 'zstd')
589
+ t.assert.ok(!response.headers['content-length'], 'no content length')
590
+ t.assert.equal(zlib.zstdDecompressSync(response.rawPayload).toString('utf-8'), smallPayload)
591
+ })
592
+ })
593
+
594
+ describe('It should use a synchronous method provided by a custom `zlib` :', async () => {
595
+ test('using `gzipSync`', async (t) => {
596
+ t.plan(3)
597
+
598
+ let usedCustom = false
599
+ const customZlib = {
600
+ gzipSync: (payload, options) => {
601
+ usedCustom = true
602
+ return zlib.gzipSync(payload, options)
603
+ }
604
+ }
605
+
606
+ const fastify = Fastify()
607
+ await fastify.register(compressPlugin, { global: true, syncThreshold, zlib: customZlib })
608
+
609
+ fastify.get('/', (_request, reply) => {
610
+ reply.type('text/plain').send(smallPayload)
611
+ })
612
+
613
+ const response = await fastify.inject({
614
+ url: '/',
615
+ method: 'GET',
616
+ headers: { 'accept-encoding': 'gzip' }
617
+ })
618
+ t.assert.equal(usedCustom, true)
619
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
620
+ t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), smallPayload)
621
+ })
622
+ })
623
+
624
+ describe('It should pass already compressed payloads through untouched :', async () => {
625
+ test('when the payload is already gzipped', async (t) => {
626
+ t.plan(3)
627
+
628
+ const gzipped = zlib.gzipSync(incompressiblePayload)
629
+
630
+ const fastify = Fastify()
631
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
632
+
633
+ fastify.get('/', (_request, reply) => {
634
+ reply.type('application/octet-stream').send(gzipped)
635
+ })
636
+
637
+ const response = await fastify.inject({
638
+ url: '/',
639
+ method: 'GET',
640
+ headers: { 'accept-encoding': 'gzip' }
641
+ })
642
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
643
+ t.assert.deepEqual(response.rawPayload, gzipped)
644
+ t.assert.deepEqual(zlib.gunzipSync(response.rawPayload), incompressiblePayload)
645
+ })
646
+
647
+ test('when the payload is already deflated', async (t) => {
648
+ t.plan(3)
649
+
650
+ const deflated = zlib.deflateSync(incompressiblePayload)
651
+
652
+ const fastify = Fastify()
653
+ await fastify.register(compressPlugin, { global: true, syncThreshold })
654
+
655
+ fastify.get('/', (_request, reply) => {
656
+ reply.type('application/octet-stream').send(deflated)
657
+ })
658
+
659
+ const response = await fastify.inject({
660
+ url: '/',
661
+ method: 'GET',
662
+ headers: { 'accept-encoding': 'deflate' }
663
+ })
664
+ t.assert.equal(response.headers['content-encoding'], 'deflate')
665
+ t.assert.deepEqual(response.rawPayload, deflated)
666
+ t.assert.deepEqual(zlib.inflateSync(response.rawPayload), incompressiblePayload)
667
+ })
668
+
669
+ test('when the payload is already gzipped using `reply.compress()`', async (t) => {
670
+ t.plan(2)
671
+
672
+ const gzipped = zlib.gzipSync(incompressiblePayload)
673
+
674
+ const fastify = Fastify()
675
+ await fastify.register(compressPlugin, { global: false, syncThreshold })
676
+
677
+ fastify.get('/', (_request, reply) => {
678
+ reply.type('application/octet-stream').compress(gzipped)
679
+ })
680
+
681
+ const response = await fastify.inject({
682
+ url: '/',
683
+ method: 'GET',
684
+ headers: { 'accept-encoding': 'gzip' }
685
+ })
686
+ t.assert.equal(response.headers['content-encoding'], 'gzip')
687
+ t.assert.deepEqual(response.rawPayload, gzipped)
688
+ })
689
+ })
690
+
691
+ describe('It should handle synchronous compression errors :', async () => {
692
+ test('using `onSend` hook', async (t) => {
693
+ t.plan(2)
694
+
695
+ const customZlib = {
696
+ gzipSync: () => {
697
+ throw new Error('sync compression failed')
698
+ }
699
+ }
700
+
701
+ const fastify = Fastify()
702
+ await fastify.register(compressPlugin, { global: true, syncThreshold, zlib: customZlib })
703
+
704
+ fastify.get('/', (_request, reply) => {
705
+ reply.type('text/plain').send(smallPayload)
706
+ })
707
+
708
+ const response = await fastify.inject({
709
+ url: '/',
710
+ method: 'GET',
711
+ headers: { 'accept-encoding': 'gzip' }
712
+ })
713
+ t.assert.equal(response.statusCode, 500)
714
+ t.assert.equal(JSON.parse(response.payload).message, 'sync compression failed')
715
+ })
716
+
717
+ test('using `reply.compress()`', async (t) => {
718
+ t.plan(2)
719
+
720
+ const customZlib = {
721
+ gzipSync: () => {
722
+ throw new Error('sync compression failed')
723
+ }
724
+ }
725
+
726
+ const fastify = Fastify()
727
+ await fastify.register(compressPlugin, { global: false, syncThreshold, zlib: customZlib })
728
+
729
+ fastify.get('/', (_request, reply) => {
730
+ reply.type('text/plain').compress(smallPayload)
731
+ })
732
+
733
+ const response = await fastify.inject({
734
+ url: '/',
735
+ method: 'GET',
736
+ headers: { 'accept-encoding': 'gzip' }
737
+ })
738
+ t.assert.equal(response.statusCode, 500)
739
+ t.assert.equal(JSON.parse(response.payload).message, 'sync compression failed')
740
+ })
741
+ })