This commit is contained in:
@@ -5,6 +5,7 @@ INITIAL_ADMIN_EMAIL=
|
|||||||
INITIAL_ADMIN_PASSWORD=
|
INITIAL_ADMIN_PASSWORD=
|
||||||
INITIAL_SELLER_NAME=
|
INITIAL_SELLER_NAME=
|
||||||
MFA_ENCRYPTION_KEY=
|
MFA_ENCRYPTION_KEY=
|
||||||
|
PRICING_FIXTURE_PROVIDER_ENABLED=false
|
||||||
PASSWORD_RESET_URL=http://localhost:5173/reset-password
|
PASSWORD_RESET_URL=http://localhost:5173/reset-password
|
||||||
SMTP_FROM=no-reply@example.test
|
SMTP_FROM=no-reply@example.test
|
||||||
SMTP_URL=smtp://localhost:1025
|
SMTP_URL=smtp://localhost:1025
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ jobs:
|
|||||||
SMTP_URL=smtp://localhost:1025
|
SMTP_URL=smtp://localhost:1025
|
||||||
SMTP_FROM=no-reply@example.test
|
SMTP_FROM=no-reply@example.test
|
||||||
MFA_ENCRYPTION_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
MFA_ENCRYPTION_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||||
|
PRICING_FIXTURE_PROVIDER_ENABLED=true
|
||||||
INITIAL_ADMIN_EMAIL=ci-admin@example.test
|
INITIAL_ADMIN_EMAIL=ci-admin@example.test
|
||||||
INITIAL_ADMIN_PASSWORD=CiPassword123!
|
INITIAL_ADMIN_PASSWORD=CiPassword123!
|
||||||
INITIAL_SELLER_NAME=CI Catalog Owner
|
INITIAL_SELLER_NAME=CI Catalog Owner
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import { createPricingService } from '../modules/pricing/services/pricing-servic
|
|||||||
import { createPricingRefreshJobRepository } from '../modules/pricing/repositories/pricing-refresh-job-repository.js';
|
import { createPricingRefreshJobRepository } from '../modules/pricing/repositories/pricing-refresh-job-repository.js';
|
||||||
import { createProviderMappingRepository } from '../modules/pricing/repositories/provider-mapping-repository.js';
|
import { createProviderMappingRepository } from '../modules/pricing/repositories/provider-mapping-repository.js';
|
||||||
import { createProviderMappingService } from '../modules/pricing/services/provider-mapping-service.js';
|
import { createProviderMappingService } from '../modules/pricing/services/provider-mapping-service.js';
|
||||||
|
import { createPricingProviderRegistry } from '../modules/pricing/providers/pricing-provider.js';
|
||||||
|
import { createFixturePricingProvider } from '../modules/pricing/providers/fixture-pricing-provider.js';
|
||||||
|
|
||||||
function parsePort(value) {
|
function parsePort(value) {
|
||||||
const port = Number.parseInt(value ?? '3000', 10);
|
const port = Number.parseInt(value ?? '3000', 10);
|
||||||
@@ -75,6 +77,9 @@ async function startServer() {
|
|||||||
const pricingRepository = createPricingRepository(database.db);
|
const pricingRepository = createPricingRepository(database.db);
|
||||||
const pricingRefreshJobRepository = createPricingRefreshJobRepository(database.db);
|
const pricingRefreshJobRepository = createPricingRefreshJobRepository(database.db);
|
||||||
const providerMappingRepository = createProviderMappingRepository(database.db);
|
const providerMappingRepository = createProviderMappingRepository(database.db);
|
||||||
|
const pricingProviders = process.env.PRICING_FIXTURE_PROVIDER_ENABLED === 'true'
|
||||||
|
? createPricingProviderRegistry([createFixturePricingProvider()])
|
||||||
|
: createPricingProviderRegistry();
|
||||||
const userRepository = createUserRepository(database.db);
|
const userRepository = createUserRepository(database.db);
|
||||||
const sessionRepository = createSessionRepository(database.db);
|
const sessionRepository = createSessionRepository(database.db);
|
||||||
const passwordResetRepository = createPasswordResetRepository(database.db);
|
const passwordResetRepository = createPasswordResetRepository(database.db);
|
||||||
@@ -139,7 +144,7 @@ async function startServer() {
|
|||||||
saleService: createSaleService({ saleRepository }),
|
saleService: createSaleService({ saleRepository }),
|
||||||
acquisitionService: createAcquisitionService({ acquisitionRepository }),
|
acquisitionService: createAcquisitionService({ acquisitionRepository }),
|
||||||
}),
|
}),
|
||||||
pricingService: createPricingService({ pricingRepository, pricingRefreshJobRepository, providerMappingRepository }),
|
pricingService: createPricingService({ pricingRepository, pricingRefreshJobRepository, providerMappingRepository, pricingProviderRegistry: pricingProviders }),
|
||||||
providerMappingService: createProviderMappingService({ providerMappingRepository }),
|
providerMappingService: createProviderMappingService({ providerMappingRepository }),
|
||||||
publicListingService: createPublicListingService({ listingRepository, publicImageService }),
|
publicListingService: createPublicListingService({ listingRepository, publicImageService }),
|
||||||
productService: createProductService({ productRepository }),
|
productService: createProductService({ productRepository }),
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { createPricingProvider } from './pricing-provider.js';
|
||||||
|
|
||||||
|
export function createFixturePricingProvider() {
|
||||||
|
return createPricingProvider({
|
||||||
|
id: 'fixture-provider',
|
||||||
|
async observe({ productId, inventoryItemId, conditionId }) {
|
||||||
|
return [{
|
||||||
|
productId,
|
||||||
|
inventoryItemId,
|
||||||
|
conditionId,
|
||||||
|
priceMinor: 2500,
|
||||||
|
currency: 'USD',
|
||||||
|
sourceIdentifier: `fixture-${inventoryItemId}`,
|
||||||
|
qualityScore: 50,
|
||||||
|
}];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
import { createPricingProvider, createPricingProviderRegistry } from '../../src/modules/pricing/providers/pricing-provider.js';
|
import { createPricingProvider, createPricingProviderRegistry } from '../../src/modules/pricing/providers/pricing-provider.js';
|
||||||
|
import { createFixturePricingProvider } from '../../src/modules/pricing/providers/fixture-pricing-provider.js';
|
||||||
|
|
||||||
test('registers pricing providers behind a validated adapter contract', async () => {
|
test('registers pricing providers behind a validated adapter contract', async () => {
|
||||||
const provider = createPricingProvider({
|
const provider = createPricingProvider({
|
||||||
@@ -19,4 +20,19 @@ test('rejects malformed provider adapters and responses', async () => {
|
|||||||
assert.throws(() => createPricingProvider({ id: 'broken' }), /observe function is required/);
|
assert.throws(() => createPricingProvider({ id: 'broken' }), /observe function is required/);
|
||||||
const provider = createPricingProvider({ id: 'broken', async observe() { return null; } });
|
const provider = createPricingProvider({ id: 'broken', async observe() { return null; } });
|
||||||
await assert.rejects(() => provider.observe({}), /must return an observation array/);
|
await assert.rejects(() => provider.observe({}), /must return an observation array/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fixture provider returns deterministic normalized observations', async () => {
|
||||||
|
const [observation] = await createFixturePricingProvider().observe({
|
||||||
|
productId: 'product-1', inventoryItemId: 'item-1', conditionId: 'condition-1',
|
||||||
|
});
|
||||||
|
assert.deepEqual(observation, {
|
||||||
|
productId: 'product-1',
|
||||||
|
inventoryItemId: 'item-1',
|
||||||
|
conditionId: 'condition-1',
|
||||||
|
priceMinor: 2500,
|
||||||
|
currency: 'USD',
|
||||||
|
sourceIdentifier: 'fixture-item-1',
|
||||||
|
qualityScore: 50,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -43,7 +43,7 @@ const acquisitionShipping = ref('');
|
|||||||
const acquisitionTax = ref('');
|
const acquisitionTax = ref('');
|
||||||
const acquisitionDate = ref(new Date().toISOString().slice(0, 10));
|
const acquisitionDate = ref(new Date().toISOString().slice(0, 10));
|
||||||
const pricingInventoryId = ref('');
|
const pricingInventoryId = ref('');
|
||||||
const pricingProvider = ref('manual');
|
const pricingProvider = ref('fixture-provider');
|
||||||
const pricingSource = ref('');
|
const pricingSource = ref('');
|
||||||
const pricingPrice = ref('');
|
const pricingPrice = ref('');
|
||||||
const overridePrice = ref('');
|
const overridePrice = ref('');
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { createPricingRepository } from '../../api/src/modules/pricing/repositor
|
|||||||
import { createPricingRefreshJobRepository } from '../../api/src/modules/pricing/repositories/pricing-refresh-job-repository.js';
|
import { createPricingRefreshJobRepository } from '../../api/src/modules/pricing/repositories/pricing-refresh-job-repository.js';
|
||||||
import { createPricingService } from '../../api/src/modules/pricing/services/pricing-service.js';
|
import { createPricingService } from '../../api/src/modules/pricing/services/pricing-service.js';
|
||||||
import { createPricingProviderRegistry } from '../../api/src/modules/pricing/providers/pricing-provider.js';
|
import { createPricingProviderRegistry } from '../../api/src/modules/pricing/providers/pricing-provider.js';
|
||||||
|
import { createFixturePricingProvider } from '../../api/src/modules/pricing/providers/fixture-pricing-provider.js';
|
||||||
import { processNextPersistentImageDerivativeJob } from './image-derivative-worker.js';
|
import { processNextPersistentImageDerivativeJob } from './image-derivative-worker.js';
|
||||||
import { processNextPersistentPricingRefreshJob, runPricingRefreshJob } from './pricing-refresh-worker.js';
|
import { processNextPersistentPricingRefreshJob, runPricingRefreshJob } from './pricing-refresh-worker.js';
|
||||||
|
|
||||||
@@ -23,7 +24,9 @@ async function runWorker() {
|
|||||||
const imageStorage = createLocalImageStorage(imageStorageRoot);
|
const imageStorage = createLocalImageStorage(imageStorageRoot);
|
||||||
const jobRepository = createImageDerivativeJobRepository(database.db);
|
const jobRepository = createImageDerivativeJobRepository(database.db);
|
||||||
const pricingRefreshJobRepository = createPricingRefreshJobRepository(database.db);
|
const pricingRefreshJobRepository = createPricingRefreshJobRepository(database.db);
|
||||||
const pricingProviderRegistry = createPricingProviderRegistry();
|
const pricingProviderRegistry = process.env.PRICING_FIXTURE_PROVIDER_ENABLED === 'true'
|
||||||
|
? createPricingProviderRegistry([createFixturePricingProvider()])
|
||||||
|
: createPricingProviderRegistry();
|
||||||
const pricingService = createPricingService({
|
const pricingService = createPricingService({
|
||||||
pricingRepository: createPricingRepository(database.db),
|
pricingRepository: createPricingRepository(database.db),
|
||||||
pricingProviderRegistry,
|
pricingProviderRegistry,
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ Work through one bounded vertical slice at a time. The linked documentation rema
|
|||||||
- Pricing refresh requests now enqueue seller-scoped jobs and expose a safe `GET /api/v1/admin/jobs/:id` polling contract instead of doing provider work inline.
|
- Pricing refresh requests now enqueue seller-scoped jobs and expose a safe `GET /api/v1/admin/jobs/:id` polling contract instead of doing provider work inline.
|
||||||
- Product-level provider mappings now persist seller-scoped `PRODUCT`/`SEARCH` relationships and support active listing and controlled disabling.
|
- Product-level provider mappings now persist seller-scoped `PRODUCT`/`SEARCH` relationships and support active listing and controlled disabling.
|
||||||
- Pricing refresh enqueue now resolves its provider from an active Product mapping and rejects unmapped Products explicitly.
|
- Pricing refresh enqueue now resolves its provider from an active Product mapping and rejects unmapped Products explicitly.
|
||||||
|
- Local development and CI can enable a deterministic fixture provider for end-to-end refresh coverage; production provider adapters remain explicitly configured deployment boundaries.
|
||||||
- `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.
|
- `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
|
## Local Commands
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ pricing service, and recalculates the item valuation. Missing providers return
|
|||||||
an explicit unavailable response; the application never fabricates observations
|
an explicit unavailable response; the application never fabricates observations
|
||||||
or values.
|
or values.
|
||||||
|
|
||||||
|
Local development and CI may enable the deterministic `fixture-provider` with
|
||||||
|
`PRICING_FIXTURE_PROVIDER_ENABLED=true`. It exists only for repeatable
|
||||||
|
workflow/testing coverage and is disabled by default; production deployments
|
||||||
|
must register an approved external adapter instead.
|
||||||
|
|
||||||
Use officially supported APIs or permitted sources. Do not invent APIs or bypass restrictions.
|
Use officially supported APIs or permitted sources. Do not invent APIs or bypass restrictions.
|
||||||
|
|
||||||
## Provider Product Mapping
|
## Provider Product Mapping
|
||||||
|
|||||||
Reference in New Issue
Block a user