headers.test.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { describe, expect, it } from 'vitest';
  2. import { getHeaderValue, toHeaders, toV2Headers, type HeaderEntry } from '@/lib/xray/headers';
  3. // Pure-function tests for the header helpers. Snapshots were locked at
  4. // the close of the legacy class migration — at that point toHeaders and
  5. // toV2Headers were verified byte-equal to the legacy XrayCommonClass
  6. // static methods. Drift past this baseline is a regression.
  7. const headerMapCases: Array<[string, unknown]> = [
  8. ['null', null],
  9. ['undefined', undefined],
  10. ['primitive', 'not-an-object'],
  11. ['empty', {}],
  12. ['single string', { Host: 'example.test' }],
  13. ['single array', { Host: ['a.example.test'] }],
  14. ['multi array', { Accept: ['text/html', 'application/json'] }],
  15. ['mixed', { Host: 'a.example.test', 'X-Trace': ['1', '2'] }],
  16. ];
  17. describe('toHeaders', () => {
  18. for (const [label, input] of headerMapCases) {
  19. it(label, () => {
  20. expect(toHeaders(input)).toMatchSnapshot();
  21. });
  22. }
  23. });
  24. const entryCases: Array<[string, HeaderEntry[]]> = [
  25. ['empty', []],
  26. ['single', [{ name: 'Host', value: 'example.test' }]],
  27. ['duplicate name', [
  28. { name: 'Accept', value: 'text/html' },
  29. { name: 'Accept', value: 'application/json' },
  30. ]],
  31. ['empty name skipped', [
  32. { name: '', value: 'ignored' },
  33. { name: 'X-Real', value: 'kept' },
  34. ]],
  35. ['empty value skipped', [
  36. { name: 'X-Empty', value: '' },
  37. { name: 'X-Real', value: 'kept' },
  38. ]],
  39. ];
  40. describe('toV2Headers (arr=true)', () => {
  41. for (const [label, input] of entryCases) {
  42. it(label, () => {
  43. expect(toV2Headers(input, true)).toMatchSnapshot();
  44. });
  45. }
  46. });
  47. describe('toV2Headers (arr=false)', () => {
  48. for (const [label, input] of entryCases) {
  49. it(label, () => {
  50. expect(toV2Headers(input, false)).toMatchSnapshot();
  51. });
  52. }
  53. });
  54. describe('getHeaderValue lookups', () => {
  55. it('returns empty string for missing map', () => {
  56. expect(getHeaderValue(undefined, 'host')).toBe('');
  57. expect(getHeaderValue(null, 'host')).toBe('');
  58. expect(getHeaderValue({}, 'host')).toBe('');
  59. });
  60. it('finds a string-valued header case-insensitively', () => {
  61. expect(getHeaderValue({ Host: 'example.test' }, 'host')).toBe('example.test');
  62. expect(getHeaderValue({ host: 'example.test' }, 'HOST')).toBe('example.test');
  63. });
  64. it('returns first value when the header is an array', () => {
  65. expect(getHeaderValue({ Accept: ['text/html', 'application/json'] }, 'accept')).toBe('text/html');
  66. });
  67. it('returns empty string when the header has empty array', () => {
  68. expect(getHeaderValue({ Host: [] }, 'host')).toBe('');
  69. });
  70. it('returns empty string for missing header name', () => {
  71. expect(getHeaderValue({ Host: 'x' }, 'origin')).toBe('');
  72. });
  73. });