headers.test.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  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. [
  28. 'duplicate name',
  29. [
  30. { name: 'Accept', value: 'text/html' },
  31. { name: 'Accept', value: 'application/json' },
  32. ],
  33. ],
  34. [
  35. 'empty name skipped',
  36. [
  37. { name: '', value: 'ignored' },
  38. { name: 'X-Real', value: 'kept' },
  39. ],
  40. ],
  41. [
  42. 'empty value skipped',
  43. [
  44. { name: 'X-Empty', value: '' },
  45. { name: 'X-Real', value: 'kept' },
  46. ],
  47. ],
  48. ];
  49. describe('toV2Headers (arr=true)', () => {
  50. for (const [label, input] of entryCases) {
  51. it(label, () => {
  52. expect(toV2Headers(input, true)).toMatchSnapshot();
  53. });
  54. }
  55. });
  56. describe('toV2Headers (arr=false)', () => {
  57. for (const [label, input] of entryCases) {
  58. it(label, () => {
  59. expect(toV2Headers(input, false)).toMatchSnapshot();
  60. });
  61. }
  62. });
  63. describe('getHeaderValue lookups', () => {
  64. it('returns empty string for missing map', () => {
  65. expect(getHeaderValue(undefined, 'host')).toBe('');
  66. expect(getHeaderValue(null, 'host')).toBe('');
  67. expect(getHeaderValue({}, 'host')).toBe('');
  68. });
  69. it('finds a string-valued header case-insensitively', () => {
  70. expect(getHeaderValue({ Host: 'example.test' }, 'host')).toBe('example.test');
  71. expect(getHeaderValue({ host: 'example.test' }, 'HOST')).toBe('example.test');
  72. });
  73. it('returns first value when the header is an array', () => {
  74. expect(getHeaderValue({ Accept: ['text/html', 'application/json'] }, 'accept')).toBe(
  75. 'text/html',
  76. );
  77. });
  78. it('returns empty string when the header has empty array', () => {
  79. expect(getHeaderValue({ Host: [] }, 'host')).toBe('');
  80. });
  81. it('returns empty string for missing header name', () => {
  82. expect(getHeaderValue({ Host: 'x' }, 'origin')).toBe('');
  83. });
  84. });