inbound-link.test.ts 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. /// <reference types="vite/client" />
  2. import { describe, expect, it } from 'vitest';
  3. import { genVlessLink, genVmessLink } from '@/lib/xray/inbound-link';
  4. import { Inbound as LegacyInbound } from '@/models/inbound';
  5. import { InboundSchema } from '@/schemas/api/inbound';
  6. // Parity harness for the share-link extraction. For each full inbound
  7. // fixture matching the protocol under test, we:
  8. // 1. Parse with the Zod InboundSchema -> typed input for the new pure fn
  9. // 2. Construct the legacy Inbound class via Inbound.fromJson(fixture)
  10. // 3. Call both link generators with matching args
  11. // 4. Assert the URLs match byte-for-byte
  12. // Drift between the new pure fn and the legacy class method fails the
  13. // test here, before the call sites in pages/ get swapped.
  14. const fullFixtures = import.meta.glob<unknown>(
  15. './golden/fixtures/inbound-full/*.json',
  16. { eager: true, import: 'default' },
  17. );
  18. function fixtureName(path: string): string {
  19. const file = path.split('/').pop() ?? path;
  20. return file.replace(/\.json$/, '');
  21. }
  22. function fixturesForProtocol(protocol: string): Array<[string, Record<string, unknown>]> {
  23. return Object.entries(fullFixtures)
  24. .filter(([, raw]) => (raw as { protocol?: string }).protocol === protocol)
  25. .map(([path, raw]): [string, Record<string, unknown>] => [fixtureName(path), raw as Record<string, unknown>])
  26. .sort(([a], [b]) => a.localeCompare(b));
  27. }
  28. describe('genVmessLink parity', () => {
  29. const fixtures = fixturesForProtocol('vmess');
  30. expect(fixtures.length, 'need at least one vmess full-inbound fixture').toBeGreaterThan(0);
  31. for (const [name, raw] of fixtures) {
  32. it(`${name}: matches legacy Inbound.genVmessLink`, () => {
  33. const typed = InboundSchema.parse(raw);
  34. const settings = (raw as { settings: { clients: Array<{ id: string; security?: string }> } }).settings;
  35. const client = settings.clients[0];
  36. const address = 'example.test';
  37. const port = typed.port;
  38. const remark = 'parity-test';
  39. const newLink = genVmessLink({
  40. inbound: typed,
  41. address,
  42. port,
  43. forceTls: 'same',
  44. remark,
  45. clientId: client.id,
  46. security: client.security as never,
  47. externalProxy: null,
  48. });
  49. const legacy = LegacyInbound.fromJson(raw);
  50. const legacyLink = legacy.genVmessLink(address, port, 'same', remark, client.id, client.security, null);
  51. expect(newLink).toBe(legacyLink);
  52. });
  53. }
  54. });
  55. describe('genVlessLink parity', () => {
  56. const fixtures = fixturesForProtocol('vless');
  57. expect(fixtures.length, 'need at least one vless full-inbound fixture').toBeGreaterThan(0);
  58. for (const [name, raw] of fixtures) {
  59. it(`${name}: matches legacy Inbound.genVLESSLink`, () => {
  60. const typed = InboundSchema.parse(raw);
  61. const settings = (raw as { settings: { clients: Array<{ id: string; flow?: string }> } }).settings;
  62. const client = settings.clients[0];
  63. const address = 'example.test';
  64. const port = typed.port;
  65. const remark = 'parity-test';
  66. const newLink = genVlessLink({
  67. inbound: typed,
  68. address,
  69. port,
  70. forceTls: 'same',
  71. remark,
  72. clientId: client.id,
  73. flow: client.flow as never,
  74. externalProxy: null,
  75. });
  76. const legacy = LegacyInbound.fromJson(raw);
  77. const legacyLink = legacy.genVLESSLink(address, port, 'same', remark, client.id, client.flow, null);
  78. expect(newLink).toBe(legacyLink);
  79. });
  80. }
  81. });