feat: add pricing refresh worker boundary
Quality / test (push) Canceled after 0s

This commit is contained in:
2026-09-09 11:25:07 -04:00
parent 4effebd2f8
commit 39b4aab058
4 changed files with 108 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
export async function runPricingRefreshJob({ job, providerRegistry, pricingService }) {
if (!job || job.type !== 'PRICING_REFRESH') {
throw new Error('Unsupported pricing refresh job.');
}
const provider = providerRegistry?.get(job.providerId);
if (!provider) {
return {
...job,
status: 'FAILED',
error: 'Pricing provider is not configured.',
failedAt: new Date(),
};
}
try {
const observations = await provider.observe({
inventoryItemId: job.inventoryItemId,
productId: job.productId,
conditionId: job.conditionId,
});
const savedObservations = [];
for (const observation of observations) {
savedObservations.push(await pricingService.addObservation({
sellerId: job.sellerId,
...observation,
providerId: provider.id,
inventoryItemId: job.inventoryItemId,
productId: job.productId,
conditionId: job.conditionId,
}));
}
const valuation = await pricingService.calculate({
sellerId: job.sellerId,
inventoryItemId: job.inventoryItemId,
});
return {
...job,
status: 'COMPLETED',
observations: savedObservations,
valuation,
completedAt: new Date(),
};
} catch (error) {
return {
...job,
status: 'FAILED',
error: error instanceof Error ? error.message : 'Pricing refresh failed.',
failedAt: new Date(),
};
}
}
@@ -0,0 +1,48 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createPricingProvider, createPricingProviderRegistry } from '../../api/src/modules/pricing/providers/pricing-provider.js';
import { runPricingRefreshJob } from '../src/pricing-refresh-worker.js';
test('completes a pricing refresh through the provider and pricing service boundaries', async () => {
const providerRegistry = createPricingProviderRegistry([
createPricingProvider({
id: 'fixture-provider',
async observe() {
return [{ priceMinor: 2500, sourceIdentifier: 'fixture-1' }];
},
}),
]);
const saved = [];
const result = await runPricingRefreshJob({
job: {
id: 'job-1',
type: 'PRICING_REFRESH',
providerId: 'fixture-provider',
sellerId: 'seller-1',
productId: 'product-1',
inventoryItemId: 'inventory-1',
conditionId: 'condition-1',
},
providerRegistry,
pricingService: {
async addObservation(input) { saved.push(input); return input; },
async calculate() { return { valuation: { valueMinor: 2500 } }; },
},
});
assert.equal(result.status, 'COMPLETED');
assert.equal(result.valuation.valuation.valueMinor, 2500);
assert.equal(saved[0].providerId, 'fixture-provider');
});
test('fails a refresh clearly when a provider is unavailable', async () => {
const result = await runPricingRefreshJob({
job: { id: 'job-2', type: 'PRICING_REFRESH', providerId: 'missing' },
providerRegistry: createPricingProviderRegistry(),
pricingService: {},
});
assert.equal(result.status, 'FAILED');
assert.match(result.error, /not configured/);
});
+1
View File
@@ -149,6 +149,7 @@ Work through one bounded vertical slice at a time. The linked documentation rema
- No initial User/Seller is provisioned in the development database yet. Run `npm run provision:admin` after setting its required local `.env` variables.
- Listings are partially implemented: draft creation, seller-scoped reads, publication gating, lifecycle writes, archive behavior, sold/archived inventory checks, and item-owned image metadata are complete; storage-backed processing remains.
- Concrete provider adapters, asynchronous pricing refresh transport, richer exports/settings, full integration CI services, and deployment/backup automation remain incomplete to varying degrees.
- The pricing refresh worker boundary now has explicit completed/failed outcomes and routes provider observations through the pricing service; persistent refresh-job storage and a configured external provider remain deployment work.
- `npm install` currently reports dependency audit findings. Review and remediate them before production deployment; do not use `npm audit fix --force` without reviewing its changes.
## Local Commands
+6
View File
@@ -104,6 +104,12 @@ Refresh:
Bulk refresh runs through BullMQ in the worker.
The worker-side refresh boundary is implemented with explicit `COMPLETED` and
`FAILED` outcomes. It resolves a registered provider, persists normalized
observations through the pricing service, and recalculates the valuation. A
provider that is missing or fails does not produce a fabricated value. Durable
refresh-job storage and external provider configuration remain deployment work.
Provider failure should not invalidate other valid observations. The failed provider is recorded for admin diagnostics.
## Overrides