fmode-client.test.ts 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import assert from 'node:assert/strict';
  2. import test from 'node:test';
  3. import { FmodeRequestError, FmodeVocEcommerceClient } from '../src/modules/domestic-voc/upstream/fmode-client.js';
  4. test('Fmode client sends credentials in a header and never in the URL', async () => {
  5. const calls: Array<{ url: string; authorization: string | null }> = [];
  6. const client = new FmodeVocEcommerceClient({
  7. baseUrl: 'http://127.0.0.1:3000/api/voc-e-commerce',
  8. apiKey: 'private-test-key',
  9. timeoutMs: 1_000,
  10. retries: 0,
  11. fetch: async (input, init) => {
  12. calls.push({
  13. url: String(input),
  14. authorization: new Headers(init?.headers).get('authorization'),
  15. });
  16. return new Response(JSON.stringify({ code: 200, data: { items: [] } }), { status: 200 });
  17. },
  18. });
  19. await client.request('jd/search-item-list/v1', { params: { keyword: 'test' } });
  20. assert.equal(calls.length, 1);
  21. assert.equal(calls[0]?.authorization, 'Bearer private-test-key');
  22. assert.doesNotMatch(calls[0]?.url ?? '', /private-test-key/);
  23. assert.match(calls[0]?.url ?? '', /keyword=test/);
  24. });
  25. test('Fmode client retries transient gateway failures', async () => {
  26. let attempts = 0;
  27. const client = new FmodeVocEcommerceClient({
  28. baseUrl: 'http://127.0.0.1:3000/api/voc-e-commerce',
  29. apiKey: 'test-key',
  30. timeoutMs: 1_000,
  31. retries: 2,
  32. sleep: async () => undefined,
  33. fetch: async () => {
  34. attempts += 1;
  35. if (attempts === 1) return new Response('{}', { status: 503 });
  36. return new Response(JSON.stringify({ code: 200, data: { ok: true } }), { status: 200 });
  37. },
  38. });
  39. const response = await client.request<{ data: { ok: boolean } }>('jd/product-detail/v1');
  40. assert.equal(attempts, 2);
  41. assert.equal(response.data.ok, true);
  42. });
  43. test('Fmode client aborts timed out requests without leaking the key', async () => {
  44. const client = new FmodeVocEcommerceClient({
  45. baseUrl: 'http://127.0.0.1:3000/api/voc-e-commerce',
  46. apiKey: 'secret-value-that-must-not-appear',
  47. timeoutMs: 5,
  48. retries: 0,
  49. fetch: async (_input, init) => new Promise((_resolve, reject) => {
  50. init?.signal?.addEventListener('abort', () => {
  51. const error = new Error('aborted');
  52. error.name = 'AbortError';
  53. reject(error);
  54. });
  55. }),
  56. });
  57. await assert.rejects(
  58. () => client.request('jd/product-detail/v1'),
  59. (error: unknown) => {
  60. assert.ok(error instanceof FmodeRequestError);
  61. assert.match(error.message, /timed out/);
  62. assert.doesNotMatch(error.message, /secret-value/);
  63. return true;
  64. },
  65. );
  66. });