UNPKG

@mjackson/headers

Version:
730 lines (557 loc) 25.2 kB
import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { Accept } from './accept.ts'; import { AcceptEncoding } from './accept-encoding.ts'; import { AcceptLanguage } from './accept-language.ts'; import { CacheControl } from './cache-control.ts'; import { ContentDisposition } from './content-disposition.ts'; import { ContentType } from './content-type.ts'; import { Cookie } from './cookie.ts'; import { SuperHeaders } from './super-headers.ts'; import { IfNoneMatch } from './if-none-match.ts'; describe('SuperHeaders', () => { it('is an instance of Headers', () => { let headers = new SuperHeaders(); assert.ok(headers instanceof SuperHeaders); assert.ok(headers instanceof Headers); }); it('initializes with no arguments', () => { let headers = new SuperHeaders(); assert.equal(headers.get('Content-Type'), null); }); it('initializes from an object of header name/value pairs', () => { let headers = new SuperHeaders({ 'Content-Type': 'text/plain' }); assert.equal(headers.get('Content-Type'), 'text/plain'); }); it('initializes from an array of key-value pairs', () => { let headers = new SuperHeaders([ ['Content-Type', 'text/plain'], ['X-Custom', 'value'], ]); assert.equal(headers.get('Content-Type'), 'text/plain'); assert.equal(headers.get('X-Custom'), 'value'); }); it('initializes from a Headers instance', () => { let h1 = new Headers({ 'Content-Type': 'text/plain' }); let h2 = new SuperHeaders(h1); assert.equal(h2.get('Content-Type'), 'text/plain'); }); it('initializes from another SuperHeaders instance', () => { let h1 = new SuperHeaders({ 'Content-Type': 'text/plain' }); let h2 = new SuperHeaders(h1); assert.equal(h2.get('Content-Type'), 'text/plain'); }); it('initializes from a string', () => { let headers = new SuperHeaders('Content-Type: text/plain\r\nContent-Length: 42'); assert.equal(headers.get('Content-Type'), 'text/plain'); assert.equal(headers.get('Content-Length'), '42'); }); it('appends values', () => { let headers = new SuperHeaders(); headers.append('X-Custom', 'value1'); headers.append('X-Custom', 'value2'); assert.equal(headers.get('X-Custom'), 'value1, value2'); }); it('sets values', () => { let headers = new SuperHeaders(); headers.set('X-Custom', 'value1'); headers.set('X-Custom', 'value2'); assert.equal(headers.get('X-Custom'), 'value2'); }); it('deletes values', () => { let headers = new SuperHeaders({ 'X-Custom': 'value' }); headers.delete('X-Custom'); assert.equal(headers.has('X-Custom'), false); }); it('checks if a header exists', () => { let headers = new SuperHeaders({ 'X-Custom': 'value' }); assert.equal(headers.has('X-Custom'), true); assert.equal(headers.has('Content-Type'), false); // Accessing this property should not change the result of has() let _ = headers.contentType; assert.equal(headers.has('Content-Type'), false); }); it('iterates over entries', () => { let headers = new SuperHeaders({ 'Content-Type': 'text/plain', 'Content-Length': '42', }); let entries = Array.from(headers.entries()); assert.deepEqual(entries, [ ['content-type', 'text/plain'], ['content-length', '42'], ]); }); it('iterates over keys', () => { let headers = new SuperHeaders({ 'Content-Type': 'text/plain', 'Content-Length': '42', }); let keys = Array.from(headers.keys()); assert.deepEqual(keys, ['content-type', 'content-length']); }); it('iterates over set-cookie keys correctly', () => { let headers = new SuperHeaders(); headers.append('Set-Cookie', 'session=abc'); headers.append('Set-Cookie', 'theme=dark'); let keys = Array.from(headers.keys()); assert.deepEqual(keys, ['set-cookie', 'set-cookie']); }); it('iterates over values', () => { let headers = new SuperHeaders({ 'Content-Type': 'text/plain', 'Content-Length': '42', }); let values = Array.from(headers.values()); assert.deepEqual(values, ['text/plain', '42']); }); it('uses forEach correctly', () => { let headers = new SuperHeaders({ 'Content-Type': 'text/plain', 'Content-Length': '42', }); let result: [string, string][] = []; headers.forEach((value, key) => { result.push([key, value]); }); assert.deepEqual(result, [ ['content-type', 'text/plain'], ['content-length', '42'], ]); }); it('is directly iterable', () => { let headers = new SuperHeaders({ 'Content-Type': 'text/plain', 'Content-Length': '42', }); let entries = Array.from(headers); assert.deepEqual(entries, [ ['content-type', 'text/plain'], ['content-length', '42'], ]); }); it('omits empty values when stringified', () => { let headers = new SuperHeaders(); // This should appear in the string since it has a media type, it's complete headers.contentType = 'text/plain'; // This should not appear in the string since it's incomplete, missing the type headers.contentDisposition.filename = 'example.txt'; assert.equal(headers.toString(), 'Content-Type: text/plain'); }); describe('constructor property init', () => { it('handles the accept property', () => { let headers = new SuperHeaders({ accept: { 'text/html': 1, 'application/json': 0.9 } }); assert.equal(headers.get('Accept'), 'text/html,application/json;q=0.9'); }); it('handles the acceptEncoding property', () => { let headers = new SuperHeaders({ acceptEncoding: { gzip: 1, deflate: 0.8 } }); assert.equal(headers.get('Accept-Encoding'), 'gzip,deflate;q=0.8'); }); it('handles the acceptLanguage property', () => { let headers = new SuperHeaders({ acceptLanguage: { 'en-US': 1, en: 0.9 } }); assert.equal(headers.get('Accept-Language'), 'en-us,en;q=0.9'); }); it('handles the acceptRanges property', () => { let headers = new SuperHeaders({ acceptRanges: 'bytes' }); assert.equal(headers.get('Accept-Ranges'), 'bytes'); }); it('handles the age property', () => { let headers = new SuperHeaders({ age: 42 }); assert.equal(headers.get('Age'), '42'); }); it('handles the cacheControl property', () => { let headers = new SuperHeaders({ cacheControl: { public: true, maxAge: 3600 } }); assert.equal(headers.get('Cache-Control'), 'public, max-age=3600'); }); it('handles the connection property', () => { let headers = new SuperHeaders({ connection: 'close' }); assert.equal(headers.get('Connection'), 'close'); }); it('handles the contentDisposition property', () => { let headers = new SuperHeaders({ contentDisposition: { type: 'attachment', filename: 'example.txt' }, }); assert.equal(headers.get('Content-Disposition'), 'attachment; filename=example.txt'); }); it('handles the contentEncoding property', () => { let headers = new SuperHeaders({ contentEncoding: 'gzip' }); assert.equal(headers.get('Content-Encoding'), 'gzip'); }); it('handles the contentLanguage property', () => { let headers = new SuperHeaders({ contentLanguage: 'en-US' }); assert.equal(headers.get('Content-Language'), 'en-US'); }); it('handles the contentLength property', () => { let headers = new SuperHeaders({ contentLength: 42 }); assert.equal(headers.get('Content-Length'), '42'); }); it('handles the contentType property', () => { let headers = new SuperHeaders({ contentType: { mediaType: 'text/plain', charset: 'utf-8' }, }); assert.equal(headers.get('Content-Type'), 'text/plain; charset=utf-8'); }); it('handles the cookie property', () => { let headers = new SuperHeaders({ cookie: [['name', 'value']] }); assert.equal(headers.get('Cookie'), 'name=value'); }); it('handles the date property', () => { let headers = new SuperHeaders({ date: new Date('2021-01-01T00:00:00Z') }); assert.equal(headers.get('Date'), 'Fri, 01 Jan 2021 00:00:00 GMT'); }); it('handles the etag property', () => { let headers = new SuperHeaders({ etag: '"67ab43"' }); assert.equal(headers.get('ETag'), '"67ab43"'); let headers2 = new SuperHeaders({ etag: '67ab43' }); assert.equal(headers2.get('ETag'), '"67ab43"'); let headers3 = new SuperHeaders({ etag: 'W/"67ab43"' }); assert.equal(headers3.get('ETag'), 'W/"67ab43"'); }); it('handles the expires property', () => { let headers = new SuperHeaders({ expires: new Date('2021-01-01T00:00:00Z') }); assert.equal(headers.get('Expires'), 'Fri, 01 Jan 2021 00:00:00 GMT'); }); it('handles the host property', () => { let headers = new SuperHeaders({ host: 'example.com' }); assert.equal(headers.get('Host'), 'example.com'); }); it('handles the ifModifiedSince property', () => { let headers = new SuperHeaders({ ifModifiedSince: new Date('2021-01-01T00:00:00Z') }); assert.equal(headers.get('If-Modified-Since'), 'Fri, 01 Jan 2021 00:00:00 GMT'); }); it('handles the ifNoneMatch property', () => { let headers = new SuperHeaders({ ifNoneMatch: ['67ab43', '54ed21'] }); assert.equal(headers.get('If-None-Match'), '"67ab43", "54ed21"'); }); it('handles the ifUnmodifiedSince property', () => { let headers = new SuperHeaders({ ifUnmodifiedSince: new Date('2021-01-01T00:00:00Z') }); assert.equal(headers.get('If-Unmodified-Since'), 'Fri, 01 Jan 2021 00:00:00 GMT'); }); it('handles the lastModified property', () => { let headers = new SuperHeaders({ lastModified: new Date('2021-01-01T00:00:00Z') }); assert.equal(headers.get('Last-Modified'), 'Fri, 01 Jan 2021 00:00:00 GMT'); }); it('handles the location property', () => { let headers = new SuperHeaders({ location: 'https://example.com' }); assert.equal(headers.get('Location'), 'https://example.com'); }); it('handles the referer property', () => { let headers = new SuperHeaders({ referer: 'https://example.com' }); assert.equal(headers.get('Referer'), 'https://example.com'); }); it('handles the setCookie property', () => { let headers = new SuperHeaders({ setCookie: [ { name: 'session', value: 'abc', path: '/' }, { name: 'theme', value: 'dark', expires: new Date('2021-12-31T23:59:59Z') }, ], }); assert.deepEqual(headers.getSetCookie(), [ 'session=abc; Path=/', 'theme=dark; Expires=Fri, 31 Dec 2021 23:59:59 GMT', ]); }); it('stringifies unknown properties with non-string values', () => { let headers = new SuperHeaders({ unknown: 42 }); assert.equal(headers.get('Unknown'), '42'); }); }); describe('property getters and setters', () => { it('supports the accept property', () => { let headers = new SuperHeaders(); assert.ok(headers.accept instanceof Accept); headers.accept = 'text/html,application/json;q=0.9'; assert.deepEqual(headers.accept.size, 2); assert.deepEqual(headers.accept.mediaTypes, ['text/html', 'application/json']); assert.deepEqual(headers.accept.weights, [1, 0.9]); headers.accept = { 'application/json': 0.8, 'text/html': 1 }; assert.deepEqual(headers.accept.size, 2); assert.deepEqual(headers.accept.mediaTypes, ['text/html', 'application/json']); assert.deepEqual(headers.accept.weights, [1, 0.8]); headers.accept = null; assert.ok(headers.accept instanceof Accept); assert.equal(headers.accept.toString(), ''); }); it('supports the acceptEncoding property', () => { let headers = new SuperHeaders(); assert.ok(headers.acceptEncoding instanceof AcceptEncoding); headers.acceptEncoding = 'gzip, deflate'; assert.deepEqual(headers.acceptEncoding.size, 2); assert.deepEqual(headers.acceptEncoding.encodings, ['gzip', 'deflate']); assert.deepEqual(headers.acceptEncoding.weights, [1, 1]); headers.acceptEncoding = { gzip: 1, deflate: 0.8 }; assert.deepEqual(headers.acceptEncoding.size, 2); assert.deepEqual(headers.acceptEncoding.encodings, ['gzip', 'deflate']); assert.deepEqual(headers.acceptEncoding.weights, [1, 0.8]); headers.acceptEncoding = null; assert.ok(headers.acceptEncoding instanceof AcceptEncoding); assert.equal(headers.acceptEncoding.toString(), ''); }); it('supports the acceptLanguage property', () => { let headers = new SuperHeaders(); assert.ok(headers.acceptLanguage instanceof AcceptLanguage); headers.acceptLanguage = 'en-US,en;q=0.9'; assert.deepEqual(headers.acceptLanguage.size, 2); assert.deepEqual(headers.acceptLanguage.languages, ['en-us', 'en']); assert.deepEqual(headers.acceptLanguage.weights, [1, 0.9]); headers.acceptLanguage = { en: 1, 'en-US': 0.8 }; assert.deepEqual(headers.acceptLanguage.size, 2); assert.deepEqual(headers.acceptLanguage.languages, ['en', 'en-us']); assert.deepEqual(headers.acceptLanguage.weights, [1, 0.8]); headers.acceptLanguage = null; assert.ok(headers.acceptLanguage instanceof AcceptLanguage); assert.equal(headers.acceptLanguage.toString(), ''); }); it('supports the acceptRanges property', () => { let headers = new SuperHeaders(); assert.equal(headers.acceptRanges, null); headers.acceptRanges = 'bytes'; assert.equal(headers.acceptRanges, 'bytes'); headers.acceptRanges = null; assert.equal(headers.acceptRanges, null); }); it('supports the age property', () => { let headers = new SuperHeaders(); assert.equal(headers.age, null); headers.age = '42'; assert.equal(headers.age, 42); headers.age = 42; assert.equal(headers.age, 42); headers.age = null; assert.equal(headers.age, null); }); it('supports the cacheControl property', () => { let headers = new SuperHeaders(); assert.ok(headers.cacheControl instanceof CacheControl); headers.cacheControl = 'public, max-age=3600'; assert.equal(headers.cacheControl.public, true); assert.equal(headers.cacheControl.maxAge, 3600); headers.cacheControl.maxAge = 1800; assert.equal(headers.cacheControl.maxAge, 1800); headers.cacheControl = { noCache: true, noStore: true }; assert.equal(headers.cacheControl.noCache, true); assert.equal(headers.cacheControl.noStore, true); headers.cacheControl = null; assert.ok(headers.cacheControl instanceof CacheControl); assert.equal(headers.cacheControl.toString(), ''); }); it('supports the connection property', () => { let headers = new SuperHeaders(); assert.equal(headers.connection, null); headers.connection = 'close'; assert.equal(headers.connection, 'close'); headers.connection = null; assert.equal(headers.connection, null); }); it('supports the contentDisposition property', () => { let headers = new SuperHeaders(); assert.ok(headers.contentDisposition instanceof ContentDisposition); headers.contentDisposition = 'attachment; filename="example.txt"'; assert.equal(headers.contentDisposition.type, 'attachment'); assert.equal(headers.contentDisposition.filename, 'example.txt'); headers.contentDisposition.filename = 'new.txt'; assert.equal(headers.contentDisposition.filename, 'new.txt'); headers.contentDisposition = { type: 'inline', filename: 'index.html' }; assert.equal(headers.contentDisposition.type, 'inline'); assert.equal(headers.contentDisposition.filename, 'index.html'); headers.contentDisposition = null; assert.ok(headers.contentDisposition instanceof ContentDisposition); assert.equal(headers.contentDisposition.toString(), ''); }); it('supports the contentEncoding property', () => { let headers = new SuperHeaders(); assert.equal(headers.contentEncoding, null); headers.contentEncoding = 'gzip'; assert.equal(headers.contentEncoding, 'gzip'); headers.contentEncoding = ['deflate', 'gzip']; assert.equal(headers.contentEncoding, 'deflate, gzip'); headers.contentEncoding = null; assert.equal(headers.contentEncoding, null); }); it('supports the contentLanguage property', () => { let headers = new SuperHeaders(); assert.equal(headers.contentLanguage, null); headers.contentLanguage = 'en-US'; assert.equal(headers.contentLanguage, 'en-US'); headers.contentLanguage = ['en', 'fr']; assert.equal(headers.contentLanguage, 'en, fr'); headers.contentLanguage = null; assert.equal(headers.contentLanguage, null); }); it('supports the contentLength property', () => { let headers = new SuperHeaders(); assert.equal(headers.contentLength, null); headers.contentLength = '42'; assert.equal(headers.contentLength, 42); headers.contentLength = 42; assert.equal(headers.contentLength, 42); headers.contentLength = null; assert.equal(headers.contentLength, null); }); it('supports the contentType property', () => { let headers = new SuperHeaders(); assert.ok(headers.contentType instanceof ContentType); headers.contentType = 'text/plain; charset=utf-8'; assert.equal(headers.contentType.mediaType, 'text/plain'); assert.equal(headers.contentType.charset, 'utf-8'); headers.contentType.charset = 'iso-8859-1'; assert.equal(headers.contentType.charset, 'iso-8859-1'); headers.contentType = { mediaType: 'text/html' }; assert.equal(headers.contentType.mediaType, 'text/html'); headers.contentType = null; assert.ok(headers.contentType instanceof ContentType); assert.equal(headers.contentType.toString(), ''); }); it('supports the cookie property', () => { let headers = new SuperHeaders(); assert.ok(headers.cookie instanceof Cookie); headers.cookie = 'name1=value1; name2=value2'; assert.equal(headers.cookie.get('name1'), 'value1'); assert.equal(headers.cookie.get('name2'), 'value2'); headers.cookie.set('name3', 'value3'); assert.equal(headers.cookie.get('name3'), 'value3'); headers.cookie = [['name4', 'value4']]; assert.equal(headers.cookie.get('name4'), 'value4'); headers.cookie = null; assert.ok(headers.cookie instanceof Cookie); assert.equal(headers.cookie.toString(), ''); }); it('supports the date property', () => { let headers = new SuperHeaders(); assert.equal(headers.date, null); headers.date = new Date('2021-01-01T00:00:00Z'); assert.ok(headers.date instanceof Date); assert.equal(headers.date.toUTCString(), 'Fri, 01 Jan 2021 00:00:00 GMT'); headers.date = null; assert.equal(headers.date, null); }); it('supports the etag property', () => { let headers = new SuperHeaders(); assert.equal(headers.etag, null); headers.etag = '"67ab43"'; assert.equal(headers.etag, '"67ab43"'); headers.etag = '67ab43'; assert.equal(headers.etag, '"67ab43"'); headers.etag = 'W/"67ab43"'; assert.equal(headers.etag, 'W/"67ab43"'); headers.etag = ''; assert.equal(headers.etag, '""'); headers.etag = '""'; assert.equal(headers.etag, '""'); headers.etag = null; assert.equal(headers.etag, null); }); it('supports the expires property', () => { let headers = new SuperHeaders(); assert.equal(headers.expires, null); headers.expires = new Date('2021-01-01T00:00:00Z'); assert.ok(headers.expires instanceof Date); assert.equal(headers.expires.toUTCString(), 'Fri, 01 Jan 2021 00:00:00 GMT'); headers.expires = null; assert.equal(headers.expires, null); }); it('supports the host property', () => { let headers = new SuperHeaders(); assert.equal(headers.host, null); headers.host = 'example.com'; assert.equal(headers.host, 'example.com'); headers.host = null; assert.equal(headers.host, null); }); it('supports the ifModifiedSince property', () => { let headers = new SuperHeaders(); assert.equal(headers.ifModifiedSince, null); headers.ifModifiedSince = new Date('2021-01-01T00:00:00Z'); assert.ok(headers.ifModifiedSince instanceof Date); assert.equal(headers.ifModifiedSince.toUTCString(), 'Fri, 01 Jan 2021 00:00:00 GMT'); headers.ifModifiedSince = null; assert.equal(headers.ifModifiedSince, null); }); it('supports the ifNoneMatch property', () => { let headers = new SuperHeaders(); assert.ok(headers.ifNoneMatch instanceof IfNoneMatch); headers.ifNoneMatch = '"67ab43", "54ed21"'; assert.deepEqual(headers.ifNoneMatch.tags, ['"67ab43"', '"54ed21"']); headers.ifNoneMatch = ['67ab43', '54ed21']; assert.deepEqual(headers.ifNoneMatch.tags, ['"67ab43"', '"54ed21"']); headers.ifNoneMatch = { tags: ['67ab43', '54ed21'] }; assert.deepEqual(headers.ifNoneMatch.tags, ['"67ab43"', '"54ed21"']); headers.ifNoneMatch = null; assert.ok(headers.ifNoneMatch instanceof IfNoneMatch); assert.equal(headers.ifNoneMatch.toString(), ''); }); it('supports the ifUnmodifiedSince property', () => { let headers = new SuperHeaders(); assert.equal(headers.ifUnmodifiedSince, null); headers.ifUnmodifiedSince = new Date('2021-01-01T00:00:00Z'); assert.ok(headers.ifUnmodifiedSince instanceof Date); assert.equal(headers.ifUnmodifiedSince.toUTCString(), 'Fri, 01 Jan 2021 00:00:00 GMT'); headers.ifUnmodifiedSince = null; assert.equal(headers.ifUnmodifiedSince, null); }); it('supports the lastModified property', () => { let headers = new SuperHeaders(); assert.equal(headers.lastModified, null); headers.lastModified = new Date('2021-01-01T00:00:00Z'); assert.ok(headers.lastModified instanceof Date); assert.equal(headers.lastModified.toUTCString(), 'Fri, 01 Jan 2021 00:00:00 GMT'); headers.lastModified = null; assert.equal(headers.lastModified, null); }); it('supports the location property', () => { let headers = new SuperHeaders(); assert.equal(headers.location, null); headers.location = 'https://example.com'; assert.equal(headers.location, 'https://example.com'); headers.location = null; assert.equal(headers.location, null); }); it('supports the referer property', () => { let headers = new SuperHeaders(); assert.equal(headers.referer, null); headers.referer = 'https://example.com'; assert.equal(headers.referer, 'https://example.com'); headers.referer = null; assert.equal(headers.referer, null); }); it('supports the setCookie property', () => { let headers = new SuperHeaders(); assert.deepEqual(headers.setCookie, []); headers.setCookie = 'session=abc'; assert.equal(headers.setCookie.length, 1); assert.equal(headers.setCookie[0].name, 'session'); assert.equal(headers.setCookie[0].value, 'abc'); headers.setCookie = { name: 'session', value: 'def' }; assert.equal(headers.setCookie.length, 1); assert.equal(headers.setCookie[0].name, 'session'); assert.equal(headers.setCookie[0].value, 'def'); headers.setCookie = ['session=abc', 'theme=dark']; assert.equal(headers.setCookie.length, 2); assert.equal(headers.setCookie[0].name, 'session'); assert.equal(headers.setCookie[0].value, 'abc'); assert.equal(headers.setCookie[1].name, 'theme'); assert.equal(headers.setCookie[1].value, 'dark'); // Can use ...spread to add new cookies headers.setCookie = [...headers.setCookie, 'lang=en']; assert.equal(headers.setCookie.length, 3); assert.equal(headers.setCookie[2].name, 'lang'); assert.equal(headers.setCookie[2].value, 'en'); headers.setCookie = [ { name: 'session', value: 'def' }, { name: 'theme', value: 'light' }, ]; assert.equal(headers.setCookie.length, 2); assert.equal(headers.setCookie[0].name, 'session'); assert.equal(headers.setCookie[0].value, 'def'); assert.equal(headers.setCookie[1].name, 'theme'); assert.equal(headers.setCookie[1].value, 'light'); // Can use push() to add new cookies headers.setCookie.push({ name: 'lang', value: 'fr' }); assert.equal(headers.setCookie.length, 3); assert.equal(headers.setCookie[2].name, 'lang'); assert.equal(headers.setCookie[2].value, 'fr'); headers.setCookie = null; assert.deepEqual(headers.setCookie, []); }); }); });