@datadome/module-nextjs
Version:
DataDome module for Next.js applications
191 lines (171 loc) • 6.96 kB
text/typescript
import test from 'ava';
import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
import type { NextRequest } from 'next/server';
import { convertHeadersToMap, getAuthorizationLength, getCookieData, getHeadersList, stringify } from './utils';
// Mock NextRequest for testing
function createMockNextRequest(headersObj: Record<string, string>): NextRequest {
return {
headers: {
keys: () => Object.keys(headersObj)[Symbol.iterator](),
get: (key: string) => headersObj[key.toLowerCase()] ?? null,
},
} as unknown as NextRequest;
}
// Mock RequestCookies for testing
function createMockCookies(cookiesArr: Array<{ name: string; value: string }>): RequestCookies {
const cookieHeader = cookiesArr
.map((c) => `${encodeURIComponent(c.name)}=${encodeURIComponent(c.value)}`)
.join('; ');
const headers = new Headers({ cookie: cookieHeader });
return new RequestCookies(headers);
}
test('getHeadersList returns comma separated header names', (t) => {
const req = createMockNextRequest({
authorization: 'Bearer token',
'content-type': 'application/json',
'x-custom-header': 'value',
});
const result = getHeadersList(req);
t.true(result.includes('authorization'));
t.true(result.includes('content-type'));
t.true(result.includes('x-custom-header'));
t.is(result.split(',').length, 3);
});
test('getHeadersList returns empty string when no headers', (t) => {
const req = createMockNextRequest({});
t.is(getHeadersList(req), '');
});
test('getAuthorizationLength returns correct length when Authorization header is present', (t) => {
const req = createMockNextRequest({
authorization: 'Bearer mytoken123',
});
const result = getAuthorizationLength(req);
t.is(result, 'Bearer mytoken123'.length);
});
test('getAuthorizationLength returns undefined when Authorization header is missing', (t) => {
const req = createMockNextRequest({});
const result = getAuthorizationLength(req);
t.is(result, undefined);
});
test('getAuthorizationLength returns 0 for empty Authorization header', (t) => {
const req = createMockNextRequest({
authorization: '',
});
const result = getAuthorizationLength(req);
t.is(result, 0);
});
test('stringify returns URL-encoded string for all defined values', (t) => {
const obj = {
foo: 'bar',
baz: 'qux quux',
num: '123',
};
const result = stringify(obj as any);
t.true(result.includes('foo=bar'));
t.true(result.includes('baz=qux%20quux'));
t.true(result.includes('num=123'));
t.is(result.split('&').length, 3);
});
test('stringify skips keys with falsy values', (t) => {
const obj = {
foo: 'bar',
empty: '',
nil: null,
undef: undefined,
zero: 0,
valid: 'yes',
};
const result = stringify(obj as any);
t.true(result.includes('foo=bar'));
t.true(result.includes('valid=yes'));
t.false(result.includes('empty='));
t.false(result.includes('nil='));
t.false(result.includes('undef='));
t.false(result.includes('zero='));
t.is(result.split('&').length, 2);
});
test('stringify returns empty string for empty object', (t) => {
t.is(stringify({} as any), '');
});
test('stringify returns empty string for null or undefined', (t) => {
t.is(stringify(null as any), '');
t.is(stringify(undefined as any), '');
});
test('getCookieData returns value of datadome cookie if present', (t) => {
const cookies = createMockCookies([
{ name: 'foo', value: 'bar' },
{ name: 'datadome', value: 'client-id-123' },
{ name: 'baz', value: 'qux' },
]);
t.is(getCookieData(cookies), 'client-id-123');
});
test('getCookieData returns empty string if datadome cookie is not present', (t) => {
const cookies = createMockCookies([
{ name: 'foo', value: 'bar' },
{ name: 'baz', value: 'qux' },
]);
t.is(getCookieData(cookies), '');
});
test('getCookieData returns empty string if cookies iterable is empty', (t) => {
const cookies = createMockCookies([]);
t.is(getCookieData(cookies), '');
});
test('convertHeadersToMap returns empty map if listKey header is missing', (t) => {
const reqHeaders = new Headers({ host: 'example.com' });
const dataDomeResHeaders = new Headers();
const result = convertHeadersToMap(reqHeaders, dataDomeResHeaders, 'x-list');
t.true(result instanceof Map);
t.is(result.size, 0);
});
test('convertHeadersToMap returns empty map if listKey header is present but empty', (t) => {
const reqHeaders = new Headers({ host: 'example.com' });
const dataDomeResHeaders = new Headers({ 'x-list': '' });
const result = convertHeadersToMap(reqHeaders, dataDomeResHeaders, 'x-list');
t.is(result.size, 0);
});
test('convertHeadersToMap maps headers from listKey', (t) => {
const reqHeaders = new Headers({ host: 'example.com' });
const dataDomeResHeaders = new Headers({
'x-list': 'foo bar',
foo: 'foo-value',
bar: 'bar-value',
});
const result = convertHeadersToMap(reqHeaders, dataDomeResHeaders, 'x-list');
t.is(result.size, 2);
t.is(result.get('foo'), 'foo-value');
t.is(result.get('bar'), 'bar-value');
});
test('convertHeadersToMap skips headers in listKey that are missing in dataDomeResHeaders', (t) => {
const reqHeaders = new Headers({ host: 'example.com' });
const dataDomeResHeaders = new Headers({
'x-list': 'foo missing',
foo: 'foo-value',
});
const result = convertHeadersToMap(reqHeaders, dataDomeResHeaders, 'x-list');
t.is(result.size, 1);
t.is(result.get('foo'), 'foo-value');
t.false(result.has('missing'));
});
test('convertHeadersToMap replaces .vercel.app domain in set-cookie header', (t) => {
const reqHeaders = new Headers({ host: 'mydomain.com' });
const setCookieValue = 'session=abc; Domain=.vercel.app; Path=/; HttpOnly';
const dataDomeResHeaders = new Headers({
'x-list': 'set-cookie',
'set-cookie': setCookieValue,
});
const result = convertHeadersToMap(reqHeaders, dataDomeResHeaders, 'x-list');
t.is(result.size, 1);
t.true(result.has('set-cookie'));
t.false(result.get('set-cookie')!.includes('.vercel.app'));
t.true(result.get('set-cookie')!.includes('Domain=mydomain.com'));
});
test('convertHeadersToMap does not replace domain if set-cookie does not contain .vercel.app', (t) => {
const reqHeaders = new Headers({ host: 'mydomain.com' });
const setCookieValue = 'session=abc; Domain=mydomain.com; Path=/; HttpOnly';
const dataDomeResHeaders = new Headers({
'x-list': 'set-cookie',
'set-cookie': setCookieValue,
});
const result = convertHeadersToMap(reqHeaders, dataDomeResHeaders, 'x-list');
t.is(result.get('set-cookie'), setCookieValue);
});