feat: add product provider mappings
Quality / test (push) Canceled after 0s

This commit is contained in:
2026-09-09 12:58:02 -04:00
parent bab4efc6ad
commit 4d3af37cd7
17 changed files with 253 additions and 3 deletions
@@ -0,0 +1,21 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createProviderMappingService } from '../../src/modules/pricing/services/provider-mapping-service.js';
test('creates and disables seller-owned provider mappings', async () => {
const calls = [];
const service = createProviderMappingService({
providerMappingRepository: {
async createForSeller(input, sellerId) { calls.push(['create', input, sellerId]); return { status: 'CREATED', mapping: { id: 'mapping-1', ...input } }; },
async listForSeller() { return [{ id: 'mapping-1', status: 'ACTIVE' }]; },
async disableForSeller() { return { status: 'DISABLED', mapping: { id: 'mapping-1', status: 'DISABLED' } }; },
},
});
const created = await service.create({ sellerId: 'seller-1', productId: 'product-1', providerId: 'fixture', mappingType: 'PRODUCT', externalIdentifier: 'ext-1' });
assert.equal(created.id, 'mapping-1');
assert.equal(calls[0][2], 'seller-1');
assert.equal((await service.list({ sellerId: 'seller-1', productId: 'product-1' })).length, 1);
assert.equal((await service.disable({ sellerId: 'seller-1', id: 'mapping-1' })).status, 'DISABLED');
});
@@ -0,0 +1,29 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { buildApp } from '../../src/app/build-app.js';
test('protects provider mapping writes and scopes mappings to the seller', async (testContext) => {
let received;
const app = buildApp({
authenticateAdmin: async () => ({ sellerId: 'seller-1' }),
authorizeUnsafeRequest: async () => {},
providerMappingService: {
async create(input) { received = input; return { id: 'mapping-1', ...input, status: 'ACTIVE' }; },
async list() { return []; },
async disable() { return { id: 'mapping-1', status: 'DISABLED' }; },
},
});
testContext.after(() => app.close());
const response = await app.inject({
method: 'POST',
url: '/api/v1/admin/products/product-1/provider-mappings',
headers: { 'x-csrf-token': 'csrf' },
payload: { providerId: 'fixture', mappingType: 'PRODUCT', externalIdentifier: 'ext-1' },
});
assert.equal(response.statusCode, 201);
assert.equal(received.sellerId, 'seller-1');
assert.equal(response.json().data.providerId, 'fixture');
});
@@ -17,6 +17,7 @@ import {
import { createAcquisitionRepository } from '../../src/modules/acquisitions/repositories/acquisition-repository.js';
import { createPricingRepository } from '../../src/modules/pricing/repositories/pricing-repository.js';
import { createPricingRefreshJobRepository } from '../../src/modules/pricing/repositories/pricing-refresh-job-repository.js';
import { createProviderMappingRepository } from '../../src/modules/pricing/repositories/provider-mapping-repository.js';
import { createSaleRepository } from '../../src/modules/sales/repositories/sale-repository.js';
const connectionString = process.env.TEST_DATABASE_URL;
@@ -33,6 +34,7 @@ const database = createDatabase(connectionString);
const acquisitionRepository = createAcquisitionRepository(database.db);
const pricingRepository = createPricingRepository(database.db);
const pricingRefreshJobRepository = createPricingRefreshJobRepository(database.db);
const providerMappingRepository = createProviderMappingRepository(database.db);
const saleRepository = createSaleRepository(database.db);
async function createFixture() {
@@ -73,7 +75,7 @@ async function createFixture() {
}
beforeEach(async () => {
await database.db.execute(sql`TRUNCATE pricing_refresh_jobs, market_value_overrides, market_valuations, pricing_observations, sale_items, sales, acquisitions, security_audit_events, inventory_items, products, conditions, condition_profiles, categories, sellers, users CASCADE`);
await database.db.execute(sql`TRUNCATE pricing_provider_mappings, pricing_refresh_jobs, market_value_overrides, market_valuations, pricing_observations, sale_items, sales, acquisitions, security_audit_events, inventory_items, products, conditions, condition_profiles, categories, sellers, users CASCADE`);
});
after(async () => {
@@ -176,3 +178,18 @@ test('claims and completes a durable pricing refresh job', async () => {
assert.deepEqual(completed.valuation, { valueMinor: 2500 });
assert.equal(user.email, 'transactions@example.test');
});
test('persists and disables seller-owned provider mappings', async () => {
const { seller, product } = await createFixture();
const created = await providerMappingRepository.createForSeller({
productId: product.id,
providerId: 'integration',
mappingType: 'PRODUCT',
externalIdentifier: 'catalog-1',
}, seller.id);
assert.equal(created.status, 'CREATED');
assert.equal((await providerMappingRepository.listForSeller(product.id, seller.id)).length, 1);
const disabled = await providerMappingRepository.disableForSeller(created.mapping.id, seller.id);
assert.equal(disabled.status, 'DISABLED');
assert.equal((await providerMappingRepository.listForSeller(product.id, seller.id)).length, 0);
});