| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- import assert from 'node:assert/strict';
- import test from 'node:test';
- import type { ClaimedSyncJob } from '../src/modules/domestic-voc/jobs/sync-worker.js';
- import type {
- SyncJobFinalStatus,
- SyncPersistence,
- } from '../src/modules/domestic-voc/repositories/voc-ingestion.repository.js';
- import { JdSyncService, type GatewayRequestClient } from '../src/modules/domestic-voc/services/jd-sync.service.js';
- import { FmodeRequestError } from '../src/modules/domestic-voc/upstream/fmode-client.js';
- class FakePersistence implements SyncPersistence {
- products = 0;
- reviews = 0;
- progress: number[] = [];
- finished: Array<{ status: SyncJobFinalStatus; errorSummary?: string }> = [];
- requeued: string[] = [];
- events: Array<{ eventType: string; message: string }> = [];
- async upsertProduct() { this.products += 1; }
- async ensureProductStub() {}
- async upsertReviews(_workspaceId: string, _platform: string, _productId: string, reviews: unknown[]) {
- this.reviews += reviews.length;
- return reviews.length;
- }
- async setJobProgress(_jobInternalId: string, progress: number) { this.progress.push(progress); }
- async finishJob(_jobInternalId: string, status: SyncJobFinalStatus, errorSummary?: string) {
- this.finished.push({ status, ...(errorSummary ? { errorSummary } : {}) });
- }
- async requeueJob(_jobInternalId: string, errorSummary: string) { this.requeued.push(errorSummary); }
- async addJobEvent(input: { eventType: string; message: string }) {
- this.events.push({ eventType: input.eventType, message: input.message });
- }
- }
- function job(overrides: Partial<ClaimedSyncJob> = {}): ClaimedSyncJob {
- return {
- internalId: '1',
- publicId: '11111111-1111-4111-8111-111111111111',
- workspaceId: 'demashi',
- platform: 'jd',
- scopes: ['product', 'reviews'],
- productIds: ['11266507445'],
- attempts: 1,
- maxAttempts: 3,
- ...overrides,
- };
- }
- test('JD sync calls only registered company gateway paths and completes', async () => {
- const calls: Array<{ path: string; params: Record<string, unknown> | undefined }> = [];
- const gateway: GatewayRequestClient = {
- async request(path, init) {
- calls.push({ path, params: init?.params });
- if (path === 'jd/get-item-detail/v1') {
- return { code: 200, data: { code: 200, data: { item: { itemId: '11266507445', itemName: 'Product' } } } } as never;
- }
- return {
- code: 200,
- data: { code: 200, data: { comments: [{ commentId: 'c1', score: 5, content: 'Useful evidence.' }] } },
- } as never;
- },
- };
- const persistence = new FakePersistence();
- await new JdSyncService(gateway, persistence, 1).process(job());
- assert.deepEqual(calls, [
- { path: 'jd/get-item-detail/v1', params: { itemId: '11266507445' } },
- { path: 'jd/get-item-comments/v1', params: { itemId: '11266507445', page: 1 } },
- ]);
- assert.equal(persistence.products, 1);
- assert.equal(persistence.reviews, 1);
- assert.deepEqual(persistence.finished, [{ status: 'completed' }]);
- assert.equal(persistence.progress.at(-1), 100);
- });
- test('JD sync returns a safe partial status when reviews are not permitted', async () => {
- const gateway: GatewayRequestClient = {
- async request(path) {
- if (path === 'jd/get-item-detail/v1') {
- return { code: 200, data: { item: { itemId: '11266507445', itemName: 'Product' } } } as never;
- }
- throw new FmodeRequestError('Bearer secret-value raw 403 response', 403, false);
- },
- };
- const persistence = new FakePersistence();
- await new JdSyncService(gateway, persistence).process(job());
- assert.deepEqual(persistence.finished, [{
- status: 'partial',
- errorSummary: 'Data gateway permission is unavailable',
- }]);
- assert.doesNotMatch(JSON.stringify(persistence.events), /secret-value|Bearer|raw 403/);
- });
- test('JD sync requeues temporary gateway failures while attempts remain', async () => {
- const gateway: GatewayRequestClient = {
- async request() {
- throw new FmodeRequestError('network internals', undefined, true);
- },
- };
- const persistence = new FakePersistence();
- await new JdSyncService(gateway, persistence).process(job({ scopes: ['product'] }));
- assert.deepEqual(persistence.finished, []);
- assert.deepEqual(persistence.requeued, ['Data gateway is temporarily unavailable']);
- });
|