| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- import assert from 'node:assert/strict';
- import type { AddressInfo } from 'node:net';
- import test from 'node:test';
- import type { Pool } from 'pg';
- import { createApp } from '../src/app.js';
- import { loadConfig } from '../src/config/env.js';
- const config = loadConfig({
- NODE_ENV: 'test',
- HOST: '127.0.0.1',
- PORT: '4400',
- DATABASE_URL: 'postgres://runtime:password@127.0.0.1:5432/saas_voc',
- PARSE_APP_ID: 'test-app-id',
- PARSE_MASTER_KEY: 'a'.repeat(32),
- PARSE_MAINTENANCE_KEY: 'b'.repeat(32),
- PARSE_SERVER_URL: 'http://127.0.0.1:4400/parse',
- API_AUTH_MODE: 'disabled',
- FMODE_BASE_URL: 'http://127.0.0.1:3000/api/voc-e-commerce',
- FMODE_API_KEY: 'test-key',
- CORS_ORIGINS: 'http://127.0.0.1:4300',
- });
- function createMockPool(missingTable = ''): Pool {
- return {
- async query(text: string) {
- if (text.includes('to_regclass')) {
- const readiness: Record<string, string | null> = {
- workspace: 'voc.workspace',
- product: 'voc.product',
- review: 'voc.review',
- sync_job: 'voc.sync_job',
- workspace_member: 'voc.workspace_member',
- analysis_run: 'voc.analysis_run',
- action_item: 'voc.action_item',
- alert: 'voc.alert',
- audit_log: 'voc.audit_log',
- };
- if (missingTable) readiness[missingTable] = null;
- return {
- rows: [readiness],
- rowCount: 1,
- };
- }
- if (text.includes('WITH target_workspace')) {
- return {
- rows: [{
- public_id: '11111111-1111-4111-8111-111111111111',
- workspace_public_id: 'demashi',
- platform: 'jd',
- status: 'pending',
- scopes: ['product', 'reviews'],
- product_ids: ['11266507445'],
- progress: 0,
- attempts: 0,
- max_attempts: 3,
- error_summary: null,
- requested_at: '2026-07-23T00:00:00.000Z',
- started_at: null,
- completed_at: null,
- }],
- rowCount: 1,
- };
- }
- if (text.includes('FROM voc.workspace_member')) {
- return {
- rows: [{
- id: '1',
- public_id: 'demashi',
- user_external_id: 'local-admin',
- email: 'local-admin@localhost',
- display_name: 'Local Admin',
- role: 'owner',
- status: 'active',
- created_at: '2026-07-23T00:00:00.000Z',
- updated_at: '2026-07-23T00:00:00.000Z',
- }],
- rowCount: 1,
- };
- }
- if (text.includes('FROM voc.workspace')) {
- return { rows: [{ id: '1', public_id: 'demashi', case_name: 'Demashi' }], rowCount: 1 };
- }
- return { rows: [], rowCount: 0 };
- },
- } as unknown as Pool;
- }
- test('HTTP surface exposes health, empty snapshot, and sync queue contract', async () => {
- const app = createApp({ config, pool: createMockPool() });
- const server = await new Promise<ReturnType<typeof app.listen>>((resolve) => {
- const listening = app.listen(0, '127.0.0.1', () => resolve(listening));
- });
- try {
- const address = server.address() as AddressInfo;
- const baseUrl = `http://127.0.0.1:${address.port}`;
- const health = await fetch(`${baseUrl}/health`);
- assert.equal(health.status, 200);
- assert.equal((await health.json() as { database: string }).database, 'ready');
- const snapshot = await fetch(`${baseUrl}/api/domestic-voc/snapshot?workspaceId=demashi&platform=jd`);
- assert.equal(snapshot.status, 200);
- assert.deepEqual((await snapshot.json() as { products: unknown[] }).products, []);
- const sync = await fetch(`${baseUrl}/api/domestic-voc/sync`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ productIds: ['11266507445'] }),
- });
- assert.equal(sync.status, 202);
- assert.equal((await sync.json() as { job: { status: string } }).job.status, 'pending');
- } finally {
- await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
- }
- });
- test('health reports a required platform migration instead of false readiness', async () => {
- const app = createApp({ config, pool: createMockPool('action_item') });
- const server = await new Promise<ReturnType<typeof app.listen>>((resolve) => {
- const listening = app.listen(0, '127.0.0.1', () => resolve(listening));
- });
- try {
- const address = server.address() as AddressInfo;
- const response = await fetch(`http://127.0.0.1:${address.port}/health`);
- assert.equal(response.status, 503);
- const body = await response.json() as { database: string; missingTables: string[] };
- assert.equal(body.database, 'migration_required');
- assert.deepEqual(body.missingTables, ['voc.action_item']);
- } finally {
- await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
- }
- });
|