3 const assert
= require('node:assert');
4 const ContentNegotiation
= require('../../lib/content-negotiation');
5 const Enum
= require('../../lib/enum');
10 ImagePng: 'image/png',
12 TextHtml: 'text/html',
13 TextPlain: 'text/plain',
14 AppJson: 'application/json',
17 describe('ContentNegotiation', function () {
18 describe('_unpackAcceptClause', function () {
19 it('handles bare type', function () {
20 const clause
= TYPES
.TextPlain
;
22 type: TYPES
.TextPlain
,
26 const result
= ContentNegotiation
._unpackAcceptClause(clause
);
27 assert
.deepStrictEqual(result
, expected
);
29 it('handles weighted type', function () {
30 const clause
= ` ${TYPES.TextPlain}; q=0.2`;
32 type: TYPES
.TextPlain
,
36 const result
= ContentNegotiation
._unpackAcceptClause(clause
);
37 assert
.deepStrictEqual(result
, expected
);
39 it('handles type with extension parameters', function () {
40 const clause
= `${TYPES.TextPlain}; format=flowed;charset=UTF-8`;
42 type: TYPES
.TextPlain
,
49 const result
= ContentNegotiation
._unpackAcceptClause(clause
);
50 assert
.deepStrictEqual(result
, expected
);
52 it('handles type with params and weight', function () {
53 const clause
= `${TYPES.AppJson};charset=UTF-8;q=0.123;ext=local`;
62 const result
= ContentNegotiation
._unpackAcceptClause(clause
);
63 assert
.deepStrictEqual(result
, expected
);
65 it('ignores empty type', function () {
67 const expected
= undefined;
68 const result
= ContentNegotiation
._unpackAcceptClause(clause
);
69 assert
.deepStrictEqual(result
, expected
);
71 it('ignores weird param', function () {
72 const clause
= `${TYPES.AppJson};blah`;
78 const result
= ContentNegotiation
._unpackAcceptClause(clause
);
79 assert
.deepStrictEqual(result
, expected
);
81 }); // _unpackAcceptClause
83 describe('_acceptClauses', function () {
84 it('orders multiple types properly', function () {
85 const acceptHeader
= 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,text/html;level=2;q=0.4, */*;q=0.5';
87 { type: TYPES
.TextHtml
, weight: 1.0, params: { 'level': '1' } },
88 { type: TYPES
.TextHtml
, weight: 0.7, params: {} },
89 { type: TYPES
.Any
, weight: 0.4998, params: {} }, // N.B. Implementation detail
90 { type: TYPES
.TextHtml
, weight: 0.4, params: { 'level': '2' } },
91 { type: TYPES
.TextAny
, weight: 0.2999, params: {} }, // N.B. Implementation detail
93 const result
= ContentNegotiation
._acceptClauses(acceptHeader
);
94 assert
.deepStrictEqual(result
, expected
);
96 it('does the expected', function () {
97 const acceptHeader
= 'text/*;q=0.5, text/plain;q=0.7, */*;q=0.2';
99 { type: TYPES
.TextPlain
, weight: 0.7, params: {} },
100 { type: TYPES
.TextAny
, weight: 0.4999, params: {} }, // N.B. Implementation detail
101 { type: TYPES
.Any
, weight: 0.1998, params: {} }, // N.B. Implementation detail
103 const result
= ContentNegotiation
._acceptClauses(acceptHeader
);
104 assert
.deepStrictEqual(result
, expected
);
106 it('covers missing data', function () {
107 const result
= ContentNegotiation
._acceptClauses(undefined);
108 assert
.deepStrictEqual(result
, []);
110 }); // _acceptClauses
112 describe('_matchType', function () {
113 it('matches simmple', function () {
114 const result
= ContentNegotiation
._matchType(TYPES
.TextPlain
, TYPES
.TextPlain
);
115 assert
.strictEqual(result
, true);
117 it('does not match simmple', function () {
118 const result
= ContentNegotiation
._matchType(TYPES
.TextPlain
, TYPES
.TextHtml
);
119 assert
.strictEqual(result
, false);
121 it('matches subtype wildcard', function () {
122 const result
= ContentNegotiation
._matchType(TYPES
.ImageAny
, TYPES
.ImagePng
);
123 assert
.strictEqual(result
, true);
125 it('does not match subtype wildcard', function () {
126 const result
= ContentNegotiation
._matchType(TYPES
.ImageAny
, TYPES
.TextPlain
);
127 assert
.strictEqual(result
, false);
129 it('matches any wildcard', function () {
130 const result
= ContentNegotiation
._matchType(TYPES
.Any
, TYPES
.AppJson
);
131 assert
.strictEqual(result
, true);
133 it('matches main type', function () {
134 const result
= ContentNegotiation
._matchType('image', TYPES
.ImagePng
);
135 assert
.strictEqual(result
, true);
137 it('does not match main type', function () {
138 const result
= ContentNegotiation
._matchType('image', TYPES
.AppJson
);
139 assert
.strictEqual(result
, false);
143 describe('accept', function () {
144 it('accepts anything', function () {
145 const types
= [ TYPES
.AppJson
, TYPES
.TextPlain
];
146 const acceptHeader
= undefined;
147 const result
= ContentNegotiation
.accept(types
, acceptHeader
);
148 assert
.strictEqual(result
, TYPES
.AppJson
);
151 it('picks the most acceptible type', function () {
152 const types
= [ TYPES
.AppJson
, TYPES
.TextPlain
];
153 const acceptHeader
= 'text/*;q=0.5, text/html;q=0.6, text/plain;q=0.7, */*;q=0.2, image/png;q=0.0, image/gif;q=0.0';
154 const result
= ContentNegotiation
.accept(types
, acceptHeader
);
155 assert
.strictEqual(result
, TYPES
.TextPlain
);
157 it('picks the most acceptible type', function () {
158 const types
= [ TYPES
.AppJson
, TYPES
.TextPlain
];
159 const acceptHeader
= 'image/png;q=0.0, image/gif;q=0.0, text/html;q=0.6, text/*;q=0.5, text/plain;q=0.7, */*;q=0.7';
160 const result
= ContentNegotiation
.accept(types
, acceptHeader
);
161 assert
.strictEqual(result
, TYPES
.TextPlain
);
163 it('refuses to pick a zero-weight type', function () {
164 const types
= [ TYPES
.AppJson
, TYPES
.TextPlain
];
165 const acceptHeader
= 'application/*;q=0.0, unhandled/type;q=0.7, none/none;q=0.2';
166 const result
= ContentNegotiation
.accept(types
, acceptHeader
);
167 assert
.strictEqual(result
, undefined);
169 it('picks least-wild match', function () {
170 const types
= [ TYPES
.TextHtml
, TYPES
.TextPlain
];
171 const acceptHeader
= '*/*;q=0.5, text/plain;q=0.5, text/*;q=0.5';
172 const result
= ContentNegotiation
.accept(types
, acceptHeader
);
173 assert
.strictEqual(result
, TYPES
.TextPlain
);
177 describe('preferred', function () {
178 it('falls back to identity', function () {
179 const encodings
= [];
180 const header
= 'br, gzip';
181 const result
= ContentNegotiation
.preferred(encodings
, header
);
182 assert
.deepStrictEqual(result
, [Enum
.EncodingType
.Identity
]);
184 it('picks appropriate', function () {
185 const encodings
= ['br', 'identity'];
186 const header
= 'lz4, br, gzip;q=.5';
187 const result
= ContentNegotiation
.preferred(encodings
, header
);
188 assert
.deepStrictEqual(result
, [Enum
.EncodingType
.Brotli
, Enum
.EncodingType
.Identity
]);
190 it('respects forbidding', function () {
191 const encodings
= ['br'];
192 const header
= 'gzip, *;q=0';
193 const result
= ContentNegotiation
.preferred(encodings
, header
);
194 assert
.deepStrictEqual(result
, []);
196 it('respects identity', function () {
197 const encodings
= ['br'];
198 const header
= 'gzip, identity;q=0.1, *;q=0';
199 const result
= ContentNegotiation
.preferred(encodings
, header
);
200 assert
.deepStrictEqual(result
, [Enum
.EncodingType
.Identity
]);
204 }); // ContentNegotiation