feat: reconcile application vertical slices
Backfill the completed acquisitions, sales, dashboard, pricing, search, public detail, worker, migration, contracts, validation, tests, and implementation-status changes into one verified application slice.
This commit is contained in:
@@ -1,23 +1,26 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { createAcquisitionService } from '../../src/modules/acquisitions/services/acquisition-service.js';
|
||||
import { createAcquisitionService } from "../../src/modules/acquisitions/services/acquisition-service.js";
|
||||
|
||||
test('creates an owned acquisition and calculates cost basis', async () => {
|
||||
test("creates an owned acquisition and calculates cost basis", async () => {
|
||||
let received;
|
||||
const service = createAcquisitionService({
|
||||
acquisitionRepository: {
|
||||
async createForSeller(input) {
|
||||
received = input;
|
||||
return { status: 'CREATED', acquisition: { id: 'acquisition-1', ...input.acquisition } };
|
||||
return {
|
||||
status: "CREATED",
|
||||
acquisition: { id: "acquisition-1", ...input.acquisition },
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.create({
|
||||
sellerId: 'seller-1',
|
||||
inventoryItemId: 'inventory-1',
|
||||
method: 'PURCHASE',
|
||||
sellerId: "seller-1",
|
||||
inventoryItemId: "inventory-1",
|
||||
method: "PURCHASE",
|
||||
costMinor: 1000,
|
||||
shippingMinor: 150,
|
||||
taxMinor: 75,
|
||||
@@ -25,17 +28,27 @@ test('creates an owned acquisition and calculates cost basis', async () => {
|
||||
estimatedTradeValueMinor: 200,
|
||||
});
|
||||
|
||||
assert.equal(received.sellerId, 'seller-1');
|
||||
assert.equal(received.sellerId, "seller-1");
|
||||
assert.equal(result.costBasisMinor, 1075);
|
||||
});
|
||||
|
||||
test('rejects acquisition for an inventory item outside the seller account', async () => {
|
||||
test("rejects acquisition for an inventory item outside the seller account", async () => {
|
||||
const service = createAcquisitionService({
|
||||
acquisitionRepository: { async createForSeller() { return { status: 'NOT_FOUND' }; } },
|
||||
acquisitionRepository: {
|
||||
async createForSeller() {
|
||||
return { status: "NOT_FOUND" };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => service.create({ sellerId: 'seller-1', inventoryItemId: 'inventory-1', method: 'PURCHASE', costMinor: 100 }),
|
||||
(error) => error.code === 'INVENTORY_NOT_FOUND' && error.statusCode === 404,
|
||||
() =>
|
||||
service.create({
|
||||
sellerId: "seller-1",
|
||||
inventoryItemId: "inventory-1",
|
||||
method: "PURCHASE",
|
||||
costMinor: 100,
|
||||
}),
|
||||
(error) => error.code === "INVENTORY_NOT_FOUND" && error.statusCode === 404,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,20 +1,59 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { createDashboardService } from '../../src/modules/dashboard/services/dashboard-service.js';
|
||||
import { createDashboardService } from "../../src/modules/dashboard/services/dashboard-service.js";
|
||||
|
||||
test('aggregates seller dashboard counts and financial totals', async () => {
|
||||
test("aggregates seller dashboard counts and financial totals", async () => {
|
||||
const service = createDashboardService({
|
||||
productService: { async listBySellerId() { return [{ id: 'product-1' }]; } },
|
||||
inventoryItemService: { async listBySellerId() { return [{ id: 'item-1', status: 'ACTIVE' }, { id: 'item-2', status: 'SOLD' }]; } },
|
||||
listingService: { async list() { return [{ status: 'AVAILABLE', askingPriceMinor: 2500, imageCount: 1 }]; } },
|
||||
saleService: { async list() { return [{ status: 'COMPLETED', netProceedsMinor: 2200, profitMinor: 700 }]; } },
|
||||
acquisitionService: { async list() { return [{ costBasisMinor: 1500 }]; } },
|
||||
pricingRepository: { async listLatestValuationsForSeller() { return [{ inventoryItemId: 'item-1', valueMinor: 2800, calculatedAt: new Date() }]; } },
|
||||
pricingRefreshJobRepository: { async countFailedForSeller() { return 2; } },
|
||||
productService: {
|
||||
async listBySellerId() {
|
||||
return [{ id: "product-1" }];
|
||||
},
|
||||
},
|
||||
inventoryItemService: {
|
||||
async listBySellerId() {
|
||||
return [
|
||||
{ id: "item-1", status: "ACTIVE" },
|
||||
{ id: "item-2", status: "SOLD" },
|
||||
];
|
||||
},
|
||||
},
|
||||
listingService: {
|
||||
async list() {
|
||||
return [{ status: "AVAILABLE", askingPriceMinor: 2500, imageCount: 1 }];
|
||||
},
|
||||
},
|
||||
saleService: {
|
||||
async list() {
|
||||
return [
|
||||
{ status: "COMPLETED", netProceedsMinor: 2200, profitMinor: 700 },
|
||||
];
|
||||
},
|
||||
},
|
||||
acquisitionService: {
|
||||
async list() {
|
||||
return [{ costBasisMinor: 1500 }];
|
||||
},
|
||||
},
|
||||
pricingRepository: {
|
||||
async listLatestValuationsForSeller() {
|
||||
return [
|
||||
{
|
||||
inventoryItemId: "item-1",
|
||||
valueMinor: 2800,
|
||||
calculatedAt: new Date(),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
pricingRefreshJobRepository: {
|
||||
async countFailedForSeller() {
|
||||
return 2;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const dashboard = await service.get({ sellerId: 'seller-1' });
|
||||
const dashboard = await service.get({ sellerId: "seller-1" });
|
||||
assert.deepEqual(dashboard.counts, {
|
||||
products: 1,
|
||||
inventory: 2,
|
||||
@@ -35,4 +74,4 @@ test('aggregates seller dashboard counts and financial totals', async () => {
|
||||
totalProfitMinor: 700,
|
||||
totalMarketValueMinor: 2800,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,38 +1,87 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { createPricingProvider, createPricingProviderRegistry } from '../../src/modules/pricing/providers/pricing-provider.js';
|
||||
import { createFixturePricingProvider } from '../../src/modules/pricing/providers/fixture-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";
|
||||
import { createHttpPricingProvider } from "../../src/modules/pricing/providers/http-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({
|
||||
id: 'example',
|
||||
async observe() { return [{ priceMinor: 2500, sourceIdentifier: 'example-1' }]; },
|
||||
id: "example",
|
||||
async observe() {
|
||||
return [{ priceMinor: 2500, sourceIdentifier: "example-1" }];
|
||||
},
|
||||
});
|
||||
const registry = createPricingProviderRegistry([provider]);
|
||||
|
||||
assert.equal(registry.get('example'), provider);
|
||||
assert.deepEqual(await registry.get('example').observe({}), [{ priceMinor: 2500, sourceIdentifier: 'example-1' }]);
|
||||
assert.equal(registry.get("example"), provider);
|
||||
assert.deepEqual(await registry.get("example").observe({}), [
|
||||
{ priceMinor: 2500, sourceIdentifier: "example-1" },
|
||||
]);
|
||||
assert.deepEqual(registry.list(), [provider]);
|
||||
});
|
||||
|
||||
test('rejects malformed provider adapters and responses', async () => {
|
||||
assert.throws(() => createPricingProvider({ id: 'broken' }), /observe function is required/);
|
||||
const provider = createPricingProvider({ id: 'broken', async observe() { return null; } });
|
||||
await assert.rejects(() => provider.observe({}), /must return an observation array/);
|
||||
test("rejects malformed provider adapters and responses", async () => {
|
||||
assert.throws(
|
||||
() => createPricingProvider({ id: "broken" }),
|
||||
/observe function is required/,
|
||||
);
|
||||
const provider = createPricingProvider({
|
||||
id: "broken",
|
||||
async observe() {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
await assert.rejects(
|
||||
() => provider.observe({}),
|
||||
/must return an observation array/,
|
||||
);
|
||||
});
|
||||
|
||||
test('fixture provider returns deterministic normalized observations', async () => {
|
||||
test("fixture provider returns deterministic normalized observations", async () => {
|
||||
const [observation] = await createFixturePricingProvider().observe({
|
||||
productId: 'product-1', inventoryItemId: 'item-1', conditionId: 'condition-1',
|
||||
productId: "product-1",
|
||||
inventoryItemId: "item-1",
|
||||
conditionId: "condition-1",
|
||||
});
|
||||
assert.deepEqual(observation, {
|
||||
productId: 'product-1',
|
||||
inventoryItemId: 'item-1',
|
||||
conditionId: 'condition-1',
|
||||
productId: "product-1",
|
||||
inventoryItemId: "item-1",
|
||||
conditionId: "condition-1",
|
||||
priceMinor: 2500,
|
||||
currency: 'USD',
|
||||
sourceIdentifier: 'fixture-item-1',
|
||||
currency: "USD",
|
||||
sourceIdentifier: "fixture-item-1",
|
||||
qualityScore: 50,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("http provider sends mapping context and normalizes its response", async () => {
|
||||
let request;
|
||||
const provider = createHttpPricingProvider({
|
||||
id: "http-provider",
|
||||
endpoint: "https://provider.example/prices",
|
||||
token: "test-token",
|
||||
async fetchImplementation(url, options) {
|
||||
request = { url: String(url), options };
|
||||
return {
|
||||
ok: true,
|
||||
async json() {
|
||||
return { observations: [{ priceMinor: 2500 }] };
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
await provider.observe({
|
||||
externalIdentifier: "catalog-1",
|
||||
searchQuery: "Charizard",
|
||||
}),
|
||||
[{ priceMinor: 2500 }],
|
||||
);
|
||||
assert.match(request.url, /externalIdentifier=catalog-1/);
|
||||
assert.equal(request.options.headers.authorization, "Bearer test-token");
|
||||
});
|
||||
|
||||
@@ -1,77 +1,137 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { createPricingService } from '../../src/modules/pricing/services/pricing-service.js';
|
||||
import { createPricingService } from "../../src/modules/pricing/services/pricing-service.js";
|
||||
|
||||
test('records an owned pricing observation and calculates a valuation', async () => {
|
||||
test("records an owned pricing observation and calculates a valuation", async () => {
|
||||
const service = createPricingService({
|
||||
pricingRepository: {
|
||||
async addObservationForSeller({ observation, sellerId }) {
|
||||
assert.equal(sellerId, 'seller-1');
|
||||
return { status: 'CREATED', observation };
|
||||
assert.equal(sellerId, "seller-1");
|
||||
return { status: "CREATED", observation };
|
||||
},
|
||||
async markOutliersForSeller() {
|
||||
return { status: 'UPDATED' };
|
||||
return { status: "UPDATED" };
|
||||
},
|
||||
async calculateForSeller() {
|
||||
return { status: 'CALCULATED', valuation: { valueMinor: 2500 }, observations: [] };
|
||||
return {
|
||||
status: "CALCULATED",
|
||||
valuation: { valueMinor: 2500 },
|
||||
observations: [],
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const observation = await service.addObservation({
|
||||
sellerId: 'seller-1', productId: 'product-1', inventoryItemId: 'inventory-1',
|
||||
providerId: 'manual', priceMinor: 2500, sourceIdentifier: 'source-1', conditionId: 'condition-1',
|
||||
sellerId: "seller-1",
|
||||
productId: "product-1",
|
||||
inventoryItemId: "inventory-1",
|
||||
providerId: "manual",
|
||||
priceMinor: 2500,
|
||||
sourceIdentifier: "source-1",
|
||||
conditionId: "condition-1",
|
||||
});
|
||||
assert.equal(observation.priceMinor, 2500);
|
||||
const valuation = await service.calculate({ sellerId: 'seller-1', inventoryItemId: 'inventory-1' });
|
||||
const valuation = await service.calculate({
|
||||
sellerId: "seller-1",
|
||||
inventoryItemId: "inventory-1",
|
||||
});
|
||||
assert.equal(valuation.valuation.valueMinor, 2500);
|
||||
});
|
||||
|
||||
test('requires a reason for an audited market-value override', async () => {
|
||||
const service = createPricingService({ pricingRepository: { async createOverride() { throw new Error('should not run'); } } });
|
||||
test("requires a reason for an audited market-value override", async () => {
|
||||
const service = createPricingService({
|
||||
pricingRepository: {
|
||||
async createOverride() {
|
||||
throw new Error("should not run");
|
||||
},
|
||||
},
|
||||
});
|
||||
await assert.rejects(
|
||||
() => service.override({ sellerId: 'seller-1', actorUserId: 'user-1', inventoryItemId: 'inventory-1', valueMinor: 2000, reason: 'x' }),
|
||||
(error) => error.code === 'OVERRIDE_REASON_REQUIRED' && error.statusCode === 400,
|
||||
() =>
|
||||
service.override({
|
||||
sellerId: "seller-1",
|
||||
actorUserId: "user-1",
|
||||
inventoryItemId: "inventory-1",
|
||||
valueMinor: 2000,
|
||||
reason: "x",
|
||||
}),
|
||||
(error) =>
|
||||
error.code === "OVERRIDE_REASON_REQUIRED" && error.statusCode === 400,
|
||||
);
|
||||
});
|
||||
|
||||
test('fails clearly when a requested pricing provider is unavailable', async () => {
|
||||
test("fails clearly when a requested pricing provider is unavailable", async () => {
|
||||
const service = createPricingService({
|
||||
pricingRepository: {},
|
||||
pricingProviderRegistry: { get() { return null; } },
|
||||
pricingProviderRegistry: {
|
||||
get() {
|
||||
return null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => service.refresh({ sellerId: 'seller-1', inventoryItemId: 'inventory-1', providerId: 'missing' }),
|
||||
(error) => error.code === 'PRICING_PROVIDER_UNAVAILABLE' && error.statusCode === 503,
|
||||
() =>
|
||||
service.refresh({
|
||||
sellerId: "seller-1",
|
||||
inventoryItemId: "inventory-1",
|
||||
providerId: "missing",
|
||||
}),
|
||||
(error) =>
|
||||
error.code === "PRICING_PROVIDER_UNAVAILABLE" && error.statusCode === 503,
|
||||
);
|
||||
});
|
||||
|
||||
test('enqueues a seller-scoped pricing refresh and reads its status', async () => {
|
||||
test("enqueues a seller-scoped pricing refresh and reads its status", async () => {
|
||||
const service = createPricingService({
|
||||
pricingRepository: {},
|
||||
pricingRefreshJobRepository: {
|
||||
async create(input) { return { id: 'job-1', status: 'PENDING', ...input }; },
|
||||
async findByIdForSeller(id, sellerId) { return { id, sellerId, status: 'COMPLETED' }; },
|
||||
async create(input) {
|
||||
return { id: "job-1", status: "PENDING", ...input };
|
||||
},
|
||||
async findByIdForSeller(id, sellerId) {
|
||||
return { id, sellerId, status: "COMPLETED" };
|
||||
},
|
||||
},
|
||||
providerMappingRepository: {
|
||||
async findActiveForSeller() {
|
||||
return { providerId: "mapped-provider" };
|
||||
},
|
||||
},
|
||||
providerMappingRepository: { async findActiveForSeller() { return { providerId: 'mapped-provider' }; } },
|
||||
});
|
||||
|
||||
const job = await service.enqueueRefresh({ sellerId: 'seller-1', inventoryItemId: 'item-1', providerId: 'provider-1', productId: 'product-1', conditionId: 'condition-1' });
|
||||
assert.equal(job.status, 'PENDING');
|
||||
assert.equal(job.providerId, 'mapped-provider');
|
||||
assert.equal((await service.getJob({ sellerId: 'seller-1', jobId: job.id })).status, 'COMPLETED');
|
||||
const job = await service.enqueueRefresh({
|
||||
sellerId: "seller-1",
|
||||
inventoryItemId: "item-1",
|
||||
providerId: "provider-1",
|
||||
productId: "product-1",
|
||||
conditionId: "condition-1",
|
||||
});
|
||||
assert.equal(job.status, "PENDING");
|
||||
assert.equal(job.providerId, "mapped-provider");
|
||||
assert.equal(
|
||||
(await service.getJob({ sellerId: "seller-1", jobId: job.id })).status,
|
||||
"COMPLETED",
|
||||
);
|
||||
});
|
||||
|
||||
test('lists and retries seller-scoped failed pricing jobs', async () => {
|
||||
test("lists and retries seller-scoped failed pricing jobs", async () => {
|
||||
const service = createPricingService({
|
||||
pricingRepository: {},
|
||||
pricingRefreshJobRepository: {
|
||||
async listForSeller() { return [{ id: 'job-1', status: 'FAILED' }]; },
|
||||
async retryForSeller() { return { id: 'job-1', status: 'PENDING' }; },
|
||||
async listForSeller() {
|
||||
return [{ id: "job-1", status: "FAILED" }];
|
||||
},
|
||||
async retryForSeller() {
|
||||
return { id: "job-1", status: "PENDING" };
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal((await service.listJobs({ sellerId: 'seller-1' })).length, 1);
|
||||
assert.equal((await service.retryJob({ sellerId: 'seller-1', jobId: 'job-1' })).status, 'PENDING');
|
||||
});
|
||||
assert.equal((await service.listJobs({ sellerId: "seller-1" })).length, 1);
|
||||
assert.equal(
|
||||
(await service.retryJob({ sellerId: "seller-1", jobId: "job-1" })).status,
|
||||
"PENDING",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,21 +1,44 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { createProviderMappingService } from '../../src/modules/pricing/services/provider-mapping-service.js';
|
||||
import { createProviderMappingService } from "../../src/modules/pricing/services/provider-mapping-service.js";
|
||||
|
||||
test('creates and disables seller-owned provider mappings', async () => {
|
||||
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' } }; },
|
||||
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');
|
||||
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",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { createSaleService } from '../../src/modules/sales/services/sale-service.js';
|
||||
import { createSaleService } from "../../src/modules/sales/services/sale-service.js";
|
||||
|
||||
function validInput(overrides = {}) {
|
||||
return {
|
||||
sellerId: 'seller-1',
|
||||
inventoryItemId: 'inventory-1',
|
||||
saleDate: '2026-09-09',
|
||||
sellerId: "seller-1",
|
||||
inventoryItemId: "inventory-1",
|
||||
saleDate: "2026-09-09",
|
||||
soldPriceMinor: 2499,
|
||||
costBasisMinor: 1200,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('creates a sale through the repository with validated money values', async () => {
|
||||
test("creates a sale through the repository with validated money values", async () => {
|
||||
let received;
|
||||
const service = createSaleService({
|
||||
saleRepository: {
|
||||
async createForInventoryItem(input) {
|
||||
received = input;
|
||||
return {
|
||||
status: 'CREATED',
|
||||
status: "CREATED",
|
||||
sale: {
|
||||
id: 'sale-1',
|
||||
id: "sale-1",
|
||||
soldPriceMinor: 2499,
|
||||
platformFeesMinor: 100,
|
||||
paymentProcessingFeesMinor: 0,
|
||||
@@ -38,9 +38,9 @@ test('creates a sale through the repository with validated money values', async
|
||||
});
|
||||
|
||||
assert.deepEqual(await service.create(validInput()), {
|
||||
status: 'CREATED',
|
||||
status: "CREATED",
|
||||
sale: {
|
||||
id: 'sale-1',
|
||||
id: "sale-1",
|
||||
soldPriceMinor: 2499,
|
||||
platformFeesMinor: 100,
|
||||
paymentProcessingFeesMinor: 0,
|
||||
@@ -52,85 +52,105 @@ test('creates a sale through the repository with validated money values', async
|
||||
netProceedsMinor: 2399,
|
||||
profitMinor: 1199,
|
||||
});
|
||||
assert.equal(received.sale.sellerId, 'seller-1');
|
||||
assert.equal(received.sale.sellerId, "seller-1");
|
||||
assert.equal(received.sale.soldPriceMinor, 2499);
|
||||
assert.equal(received.costBasisMinor, 1200);
|
||||
});
|
||||
|
||||
test('lists seller sales with derived net proceeds and profit', async () => {
|
||||
test("lists seller sales with derived net proceeds and profit", async () => {
|
||||
const service = createSaleService({
|
||||
saleRepository: {
|
||||
async listBySellerId(sellerId) {
|
||||
assert.equal(sellerId, 'seller-1');
|
||||
return [{
|
||||
sale: { soldPriceMinor: 3000, shippingCostMinor: 200 },
|
||||
saleItem: { costBasisMinor: 1500 },
|
||||
inventoryItem: { id: 'inventory-1', status: 'SOLD' },
|
||||
}];
|
||||
assert.equal(sellerId, "seller-1");
|
||||
return [
|
||||
{
|
||||
sale: { soldPriceMinor: 3000, shippingCostMinor: 200 },
|
||||
saleItem: { costBasisMinor: 1500 },
|
||||
inventoryItem: { id: "inventory-1", status: "SOLD" },
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [sale] = await service.list({ sellerId: 'seller-1' });
|
||||
const [sale] = await service.list({ sellerId: "seller-1" });
|
||||
assert.equal(sale.netProceedsMinor, 2800);
|
||||
assert.equal(sale.profitMinor, 1300);
|
||||
});
|
||||
|
||||
test('voids an owned sale with a required reason', async () => {
|
||||
test("voids an owned sale with a required reason", async () => {
|
||||
let received;
|
||||
const service = createSaleService({
|
||||
saleRepository: {
|
||||
async voidForSeller(input) {
|
||||
received = input;
|
||||
return {
|
||||
status: 'VOIDED',
|
||||
sale: { id: 'sale-1', soldPriceMinor: 3000 },
|
||||
status: "VOIDED",
|
||||
sale: { id: "sale-1", soldPriceMinor: 3000 },
|
||||
saleItem: { costBasisMinor: 1500 },
|
||||
inventoryItem: { id: 'inventory-1', status: 'ACTIVE' },
|
||||
inventoryItem: { id: "inventory-1", status: "ACTIVE" },
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.void({
|
||||
saleId: 'sale-1',
|
||||
sellerId: 'seller-1',
|
||||
actorUserId: 'user-1',
|
||||
reason: ' Duplicate sale ',
|
||||
saleId: "sale-1",
|
||||
sellerId: "seller-1",
|
||||
actorUserId: "user-1",
|
||||
reason: " Duplicate sale ",
|
||||
});
|
||||
|
||||
assert.equal(received.reason, 'Duplicate sale');
|
||||
assert.equal(result.inventoryItem.status, 'ACTIVE');
|
||||
assert.equal(received.reason, "Duplicate sale");
|
||||
assert.equal(result.inventoryItem.status, "ACTIVE");
|
||||
await assert.rejects(
|
||||
() => service.void({ saleId: 'sale-1', sellerId: 'seller-1', actorUserId: 'user-1', reason: 'x' }),
|
||||
(error) => error.code === 'SALE_VOID_REASON_REQUIRED' && error.statusCode === 400,
|
||||
() =>
|
||||
service.void({
|
||||
saleId: "sale-1",
|
||||
sellerId: "seller-1",
|
||||
actorUserId: "user-1",
|
||||
reason: "x",
|
||||
}),
|
||||
(error) =>
|
||||
error.code === "SALE_VOID_REASON_REQUIRED" && error.statusCode === 400,
|
||||
);
|
||||
});
|
||||
|
||||
test('creates one sale containing multiple inventory items', async () => {
|
||||
test("creates one sale containing multiple inventory items", async () => {
|
||||
let received;
|
||||
const service = createSaleService({
|
||||
saleRepository: {
|
||||
async createForInventoryItems(input) {
|
||||
received = input;
|
||||
return {
|
||||
status: 'CREATED',
|
||||
sale: { id: 'sale-2', soldPriceMinor: 3000 },
|
||||
status: "CREATED",
|
||||
sale: { id: "sale-2", soldPriceMinor: 3000 },
|
||||
saleItems: input.items,
|
||||
saleItem: input.items[0],
|
||||
inventoryItems: input.items.map(({ inventoryItemId }) => ({ id: inventoryItemId, status: 'SOLD' })),
|
||||
inventoryItem: { id: input.items[0].inventoryItemId, status: 'SOLD' },
|
||||
inventoryItems: input.items.map(({ inventoryItemId }) => ({
|
||||
id: inventoryItemId,
|
||||
status: "SOLD",
|
||||
})),
|
||||
inventoryItem: { id: input.items[0].inventoryItemId, status: "SOLD" },
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.create({
|
||||
sellerId: 'seller-1',
|
||||
saleDate: '2026-09-09',
|
||||
sellerId: "seller-1",
|
||||
saleDate: "2026-09-09",
|
||||
items: [
|
||||
{ inventoryItemId: 'inventory-1', soldPriceMinor: 1200, costBasisMinor: 500 },
|
||||
{ inventoryItemId: 'inventory-2', soldPriceMinor: 1800, costBasisMinor: 700 },
|
||||
{
|
||||
inventoryItemId: "inventory-1",
|
||||
soldPriceMinor: 1200,
|
||||
costBasisMinor: 500,
|
||||
},
|
||||
{
|
||||
inventoryItemId: "inventory-2",
|
||||
soldPriceMinor: 1800,
|
||||
costBasisMinor: 700,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -139,18 +159,27 @@ test('creates one sale containing multiple inventory items', async () => {
|
||||
assert.equal(result.profitMinor, 1800);
|
||||
});
|
||||
|
||||
test('maps missing and already-sold inventory to application errors', async () => {
|
||||
test("maps missing and already-sold inventory to application errors", async () => {
|
||||
for (const result of [
|
||||
{ status: 'NOT_FOUND', code: 'INVENTORY_NOT_FOUND', statusCode: 404 },
|
||||
{ status: 'NOT_ACTIVE', code: 'INVENTORY_NOT_AVAILABLE_FOR_SALE', statusCode: 409 },
|
||||
{ status: "NOT_FOUND", code: "INVENTORY_NOT_FOUND", statusCode: 404 },
|
||||
{
|
||||
status: "NOT_ACTIVE",
|
||||
code: "INVENTORY_NOT_AVAILABLE_FOR_SALE",
|
||||
statusCode: 409,
|
||||
},
|
||||
]) {
|
||||
const service = createSaleService({
|
||||
saleRepository: { async createForInventoryItem() { return result; } },
|
||||
saleRepository: {
|
||||
async createForInventoryItem() {
|
||||
return result;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => service.create(validInput()),
|
||||
(error) => error.code === result.code && error.statusCode === result.statusCode,
|
||||
(error) =>
|
||||
error.code === result.code && error.statusCode === result.statusCode,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user