update dependencies and devDependencies, fix lint issues
[squeep-api-dingus] / test / lib / content-negotiation.js
1 'use strict';
2
3 const assert = require('node:assert');
4 const ContentNegotiation = require('../../lib/content-negotiation');
5 const Enum = require('../../lib/enum');
6
7 const TYPES = {
8 Any: '*/*',
9 ImageAny: 'image/*',
10 ImagePng: 'image/png',
11 TextAny: 'text/*',
12 TextHtml: 'text/html',
13 TextPlain: 'text/plain',
14 AppJson: 'application/json',
15 };
16
17 describe('ContentNegotiation', function () {
18 describe('_unpackAcceptClause', function () {
19 it('handles bare type', function () {
20 const clause = TYPES.TextPlain;
21 const expected = {
22 type: TYPES.TextPlain,
23 weight: 1.0,
24 params: {},
25 };
26 const result = ContentNegotiation._unpackAcceptClause(clause);
27 assert.deepStrictEqual(result, expected);
28 });
29 it('handles weighted type', function () {
30 const clause = ` ${TYPES.TextPlain}; q=0.2`;
31 const expected = {
32 type: TYPES.TextPlain,
33 weight: 0.2,
34 params: {},
35 };
36 const result = ContentNegotiation._unpackAcceptClause(clause);
37 assert.deepStrictEqual(result, expected);
38 });
39 it('handles type with extension parameters', function () {
40 const clause = `${TYPES.TextPlain}; format=flowed;charset=UTF-8`;
41 const expected = {
42 type: TYPES.TextPlain,
43 weight: 1.0,
44 params: {
45 format: 'flowed',
46 charset: 'UTF-8',
47 },
48 };
49 const result = ContentNegotiation._unpackAcceptClause(clause);
50 assert.deepStrictEqual(result, expected);
51 });
52 it('handles type with params and weight', function () {
53 const clause = `${TYPES.AppJson};charset=UTF-8;q=0.123;ext=local`;
54 const expected = {
55 type: TYPES.AppJson,
56 weight: 0.123,
57 params: {
58 charset: 'UTF-8',
59 ext: 'local',
60 },
61 };
62 const result = ContentNegotiation._unpackAcceptClause(clause);
63 assert.deepStrictEqual(result, expected);
64 });
65 it('ignores empty type', function () {
66 const clause = '';
67 const expected = undefined;
68 const result = ContentNegotiation._unpackAcceptClause(clause);
69 assert.deepStrictEqual(result, expected);
70 });
71 it('ignores weird param', function () {
72 const clause = `${TYPES.AppJson};blah`;
73 const expected = {
74 type: TYPES.AppJson,
75 weight: 1.0,
76 params: {},
77 };
78 const result = ContentNegotiation._unpackAcceptClause(clause);
79 assert.deepStrictEqual(result, expected);
80 });
81 }); // _unpackAcceptClause
82
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';
86 const expected = [
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
92 ];
93 const result = ContentNegotiation._acceptClauses(acceptHeader);
94 assert.deepStrictEqual(result, expected);
95 });
96 it('does the expected', function () {
97 const acceptHeader = 'text/*;q=0.5, text/plain;q=0.7, */*;q=0.2';
98 const expected = [
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
102 ];
103 const result = ContentNegotiation._acceptClauses(acceptHeader);
104 assert.deepStrictEqual(result, expected);
105 });
106 it('covers missing data', function () {
107 const result = ContentNegotiation._acceptClauses(undefined);
108 assert.deepStrictEqual(result, []);
109 });
110 }); // _acceptClauses
111
112 describe('_matchType', function () {
113 it('matches simmple', function () {
114 const result = ContentNegotiation._matchType(TYPES.TextPlain, TYPES.TextPlain);
115 assert.strictEqual(result, true);
116 });
117 it('does not match simmple', function () {
118 const result = ContentNegotiation._matchType(TYPES.TextPlain, TYPES.TextHtml);
119 assert.strictEqual(result, false);
120 });
121 it('matches subtype wildcard', function () {
122 const result = ContentNegotiation._matchType(TYPES.ImageAny, TYPES.ImagePng);
123 assert.strictEqual(result, true);
124 });
125 it('does not match subtype wildcard', function () {
126 const result = ContentNegotiation._matchType(TYPES.ImageAny, TYPES.TextPlain);
127 assert.strictEqual(result, false);
128 });
129 it('matches any wildcard', function () {
130 const result = ContentNegotiation._matchType(TYPES.Any, TYPES.AppJson);
131 assert.strictEqual(result, true);
132 });
133 it('matches main type', function () {
134 const result = ContentNegotiation._matchType('image', TYPES.ImagePng);
135 assert.strictEqual(result, true);
136 });
137 it('does not match main type', function () {
138 const result = ContentNegotiation._matchType('image', TYPES.AppJson);
139 assert.strictEqual(result, false);
140 });
141 }); // _matchType
142
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);
149 });
150
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);
156 });
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);
162 });
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);
168 });
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);
174 });
175 }); // accept
176
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]);
183 });
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]);
189 });
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, []);
195 });
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]);
201 });
202 }); // preferred
203
204 }); // ContentNegotiation
205