app.test.ts 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import assert from 'node:assert/strict';
  2. import type { AddressInfo } from 'node:net';
  3. import test from 'node:test';
  4. import type { Pool } from 'pg';
  5. import { createApp } from '../src/app.js';
  6. import { loadConfig } from '../src/config/env.js';
  7. const config = loadConfig({
  8. NODE_ENV: 'test',
  9. HOST: '127.0.0.1',
  10. PORT: '4400',
  11. DATABASE_URL: 'postgres://runtime:password@127.0.0.1:5432/saas_voc',
  12. PARSE_APP_ID: 'test-app-id',
  13. PARSE_MASTER_KEY: 'a'.repeat(32),
  14. PARSE_MAINTENANCE_KEY: 'b'.repeat(32),
  15. PARSE_SERVER_URL: 'http://127.0.0.1:4400/parse',
  16. FMODE_BASE_URL: 'http://127.0.0.1:3000/api/voc-e-commerce',
  17. FMODE_API_KEY: 'test-key',
  18. CORS_ORIGINS: 'http://127.0.0.1:4300',
  19. });
  20. function createMockPool(): Pool {
  21. return {
  22. async query(text: string) {
  23. if (text.includes('to_regclass')) {
  24. return { rows: [{ schema_ready: 'voc.workspace' }], rowCount: 1 };
  25. }
  26. if (text.includes('WITH target_workspace')) {
  27. return {
  28. rows: [{
  29. public_id: '11111111-1111-4111-8111-111111111111',
  30. workspace_public_id: 'demashi',
  31. platform: 'jd',
  32. status: 'pending',
  33. scopes: ['product', 'reviews'],
  34. product_ids: ['11266507445'],
  35. progress: 0,
  36. attempts: 0,
  37. max_attempts: 3,
  38. error_summary: null,
  39. requested_at: '2026-07-23T00:00:00.000Z',
  40. started_at: null,
  41. completed_at: null,
  42. }],
  43. rowCount: 1,
  44. };
  45. }
  46. if (text.includes('FROM voc.workspace')) {
  47. return { rows: [{ id: '1', public_id: 'demashi', case_name: 'Demashi' }], rowCount: 1 };
  48. }
  49. return { rows: [], rowCount: 0 };
  50. },
  51. } as unknown as Pool;
  52. }
  53. test('HTTP surface exposes health, empty snapshot, and sync queue contract', async () => {
  54. const app = createApp({ config, pool: createMockPool() });
  55. const server = await new Promise<ReturnType<typeof app.listen>>((resolve) => {
  56. const listening = app.listen(0, '127.0.0.1', () => resolve(listening));
  57. });
  58. try {
  59. const address = server.address() as AddressInfo;
  60. const baseUrl = `http://127.0.0.1:${address.port}`;
  61. const health = await fetch(`${baseUrl}/health`);
  62. assert.equal(health.status, 200);
  63. assert.equal((await health.json() as { database: string }).database, 'ready');
  64. const snapshot = await fetch(`${baseUrl}/api/domestic-voc/snapshot?workspaceId=demashi&platform=jd`);
  65. assert.equal(snapshot.status, 200);
  66. assert.deepEqual((await snapshot.json() as { products: unknown[] }).products, []);
  67. const sync = await fetch(`${baseUrl}/api/domestic-voc/sync`, {
  68. method: 'POST',
  69. headers: { 'Content-Type': 'application/json' },
  70. body: JSON.stringify({ productIds: ['11266507445'] }),
  71. });
  72. assert.equal(sync.status, 202);
  73. assert.equal((await sync.json() as { job: { status: string } }).job.status, 'pending');
  74. } finally {
  75. await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  76. }
  77. });