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,7 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildApp } from '../../src/app/build-app.js';
|
||||
import { buildApp } from "../../src/app/build-app.js";
|
||||
|
||||
function createTestApp({ authenticateAdmin, authorizeUnsafeRequest } = {}) {
|
||||
return buildApp({
|
||||
@@ -9,7 +9,7 @@ function createTestApp({ authenticateAdmin, authorizeUnsafeRequest } = {}) {
|
||||
authorizeUnsafeRequest,
|
||||
acquisitionService: {
|
||||
async create(input) {
|
||||
return { id: 'acquisition-1', ...input, costBasisMinor: 1075 };
|
||||
return { id: "acquisition-1", ...input, costBasisMinor: 1075 };
|
||||
},
|
||||
async list() {
|
||||
return [];
|
||||
@@ -18,21 +18,23 @@ function createTestApp({ authenticateAdmin, authorizeUnsafeRequest } = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
test('creates an acquisition for the authenticated seller', async (testContext) => {
|
||||
test("creates an acquisition for the authenticated seller", async (testContext) => {
|
||||
let csrfChecked = false;
|
||||
const app = createTestApp({
|
||||
authenticateAdmin: async () => ({ sellerId: 'seller-1' }),
|
||||
authorizeUnsafeRequest: async () => { csrfChecked = true; },
|
||||
authenticateAdmin: async () => ({ sellerId: "seller-1" }),
|
||||
authorizeUnsafeRequest: async () => {
|
||||
csrfChecked = true;
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/admin/acquisitions',
|
||||
headers: { 'x-csrf-token': 'csrf-token' },
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/acquisitions",
|
||||
headers: { "x-csrf-token": "csrf-token" },
|
||||
payload: {
|
||||
inventoryItemId: 'd2f67d74-9647-48d5-b3a4-74d3b16ea9f8',
|
||||
method: 'PURCHASE',
|
||||
inventoryItemId: "d2f67d74-9647-48d5-b3a4-74d3b16ea9f8",
|
||||
method: "PURCHASE",
|
||||
costMinor: 1000,
|
||||
},
|
||||
});
|
||||
@@ -42,16 +44,18 @@ test('creates an acquisition for the authenticated seller', async (testContext)
|
||||
assert.equal(csrfChecked, true);
|
||||
});
|
||||
|
||||
test('rejects invalid acquisition requests', async (testContext) => {
|
||||
const app = createTestApp({ authenticateAdmin: async () => ({ sellerId: 'seller-1' }) });
|
||||
test("rejects invalid acquisition requests", async (testContext) => {
|
||||
const app = createTestApp({
|
||||
authenticateAdmin: async () => ({ sellerId: "seller-1" }),
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/admin/acquisitions',
|
||||
payload: { inventoryItemId: 'not-a-uuid', method: '', costMinor: -1 },
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/acquisitions",
|
||||
payload: { inventoryItemId: "not-a-uuid", method: "", costMinor: -1 },
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 400);
|
||||
assert.equal(response.json().error.code, 'VALIDATION_ERROR');
|
||||
});
|
||||
assert.equal(response.json().error.code, "VALIDATION_ERROR");
|
||||
});
|
||||
|
||||
@@ -1,16 +1,34 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildApp } from '../../src/app/build-app.js';
|
||||
import { buildApp } from "../../src/app/build-app.js";
|
||||
|
||||
test('returns a seller-scoped dashboard', async (testContext) => {
|
||||
test("returns a seller-scoped dashboard", async (testContext) => {
|
||||
const app = buildApp({
|
||||
authenticateAdmin: async () => ({ sellerId: 'seller-1' }),
|
||||
dashboardService: { async get({ sellerId }) { assert.equal(sellerId, 'seller-1'); return { counts: {}, financials: {}, recentSales: [], recentAcquisitions: [] }; } },
|
||||
authenticateAdmin: async () => ({ sellerId: "seller-1" }),
|
||||
dashboardService: {
|
||||
async get({ sellerId }) {
|
||||
assert.equal(sellerId, "seller-1");
|
||||
return {
|
||||
counts: {},
|
||||
financials: {},
|
||||
recentSales: [],
|
||||
recentAcquisitions: [],
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({ method: 'GET', url: '/api/v1/admin/dashboard' });
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/v1/admin/dashboard",
|
||||
});
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.deepEqual(response.json().data, { counts: {}, financials: {}, recentSales: [], recentAcquisitions: [] });
|
||||
});
|
||||
assert.deepEqual(response.json().data, {
|
||||
counts: {},
|
||||
financials: {},
|
||||
recentSales: [],
|
||||
recentAcquisitions: [],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,25 +1,54 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildApp } from '../../src/app/build-app.js';
|
||||
import { buildApp } from "../../src/app/build-app.js";
|
||||
|
||||
test('accepts a protected pricing observation and valuation request', async (testContext) => {
|
||||
test("accepts a protected pricing observation and valuation request", async (testContext) => {
|
||||
let csrfChecks = 0;
|
||||
const app = buildApp({
|
||||
authenticateAdmin: async () => ({ sellerId: 'seller-1' }),
|
||||
authorizeUnsafeRequest: async () => { csrfChecks += 1; },
|
||||
authenticateAdmin: async () => ({ sellerId: "seller-1" }),
|
||||
authorizeUnsafeRequest: async () => {
|
||||
csrfChecks += 1;
|
||||
},
|
||||
pricingService: {
|
||||
async addObservation(input) { return { id: 'observation-1', ...input }; },
|
||||
async calculate() { return { valuation: { id: 'valuation-1', inventoryItemId: 'inventory-1', valueMinor: 2500, currency: 'USD', strategy: 'MEDIAN', confidence: 'LOW', sourceCount: 1 }, observations: [] }; },
|
||||
async addObservation(input) {
|
||||
return { id: "observation-1", ...input };
|
||||
},
|
||||
async calculate() {
|
||||
return {
|
||||
valuation: {
|
||||
id: "valuation-1",
|
||||
inventoryItemId: "inventory-1",
|
||||
valueMinor: 2500,
|
||||
currency: "USD",
|
||||
strategy: "MEDIAN",
|
||||
confidence: "LOW",
|
||||
sourceCount: 1,
|
||||
},
|
||||
observations: [],
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const observationResponse = await app.inject({
|
||||
method: 'POST', url: '/api/v1/admin/pricing/observations', headers: { 'x-csrf-token': 'csrf' },
|
||||
payload: { productId: 'd2f67d74-9647-48d5-b3a4-74d3b16ea9f8', inventoryItemId: '21eb218b-7a95-4f80-a9e3-e306f7ca1f2e', providerId: 'manual', priceMinor: 2500, sourceIdentifier: 'source-1', conditionId: 'ab8150c7-96c1-4b55-8d69-7d0b77d48958' },
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/pricing/observations",
|
||||
headers: { "x-csrf-token": "csrf" },
|
||||
payload: {
|
||||
productId: "d2f67d74-9647-48d5-b3a4-74d3b16ea9f8",
|
||||
inventoryItemId: "21eb218b-7a95-4f80-a9e3-e306f7ca1f2e",
|
||||
providerId: "manual",
|
||||
priceMinor: 2500,
|
||||
sourceIdentifier: "source-1",
|
||||
conditionId: "ab8150c7-96c1-4b55-8d69-7d0b77d48958",
|
||||
},
|
||||
});
|
||||
const valuationResponse = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/inventory/21eb218b-7a95-4f80-a9e3-e306f7ca1f2e/valuation",
|
||||
});
|
||||
const valuationResponse = await app.inject({ method: 'POST', url: '/api/v1/admin/inventory/21eb218b-7a95-4f80-a9e3-e306f7ca1f2e/valuation' });
|
||||
|
||||
assert.equal(observationResponse.statusCode, 201);
|
||||
assert.equal(valuationResponse.statusCode, 200);
|
||||
@@ -27,59 +56,74 @@ test('accepts a protected pricing observation and valuation request', async (tes
|
||||
assert.equal(csrfChecks, 2);
|
||||
});
|
||||
|
||||
test('accepts a CSRF-protected market-value override', async (testContext) => {
|
||||
test("accepts a CSRF-protected market-value override", async (testContext) => {
|
||||
let received;
|
||||
const app = buildApp({
|
||||
authenticateAdmin: async () => ({ userId: 'user-1', sellerId: 'seller-1' }),
|
||||
authenticateAdmin: async () => ({ userId: "user-1", sellerId: "seller-1" }),
|
||||
authorizeUnsafeRequest: async () => {},
|
||||
pricingService: { async override(input) { received = input; return { id: 'override-1', ...input }; } },
|
||||
pricingService: {
|
||||
async override(input) {
|
||||
received = input;
|
||||
return { id: "override-1", ...input };
|
||||
},
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/admin/inventory/21eb218b-7a95-4f80-a9e3-e306f7ca1f2e/valuation/override',
|
||||
headers: { 'x-csrf-token': 'csrf' },
|
||||
payload: { valueMinor: 2600, reason: 'Verified recent sale' },
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/inventory/21eb218b-7a95-4f80-a9e3-e306f7ca1f2e/valuation/override",
|
||||
headers: { "x-csrf-token": "csrf" },
|
||||
payload: { valueMinor: 2600, reason: "Verified recent sale" },
|
||||
});
|
||||
assert.equal(response.statusCode, 201);
|
||||
assert.equal(received.actorUserId, 'user-1');
|
||||
assert.equal(received.actorUserId, "user-1");
|
||||
assert.equal(response.json().data.valueMinor, 2600);
|
||||
});
|
||||
|
||||
test('enqueues a pricing refresh and exposes seller-scoped job status', async (testContext) => {
|
||||
test("enqueues a pricing refresh and exposes seller-scoped job status", async (testContext) => {
|
||||
const app = buildApp({
|
||||
authenticateAdmin: async () => ({ userId: 'user-1', sellerId: 'seller-1' }),
|
||||
authenticateAdmin: async () => ({ userId: "user-1", sellerId: "seller-1" }),
|
||||
authorizeUnsafeRequest: async () => {},
|
||||
pricingService: {
|
||||
async enqueueRefresh(input) { return { id: 'job-1', status: 'PENDING', createdAt: input.sellerId }; },
|
||||
async getJob(input) { assert.equal(input.sellerId, 'seller-1'); return { id: input.jobId, status: 'COMPLETED', error: null }; },
|
||||
async enqueueRefresh(input) {
|
||||
return { id: "job-1", status: "PENDING", createdAt: input.sellerId };
|
||||
},
|
||||
async getJob(input) {
|
||||
assert.equal(input.sellerId, "seller-1");
|
||||
return { id: input.jobId, status: "COMPLETED", error: null };
|
||||
},
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/admin/inventory/inventory-1/pricing/refresh',
|
||||
headers: { 'x-csrf-token': 'csrf' },
|
||||
payload: { providerId: 'missing' },
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/inventory/inventory-1/pricing/refresh",
|
||||
headers: { "x-csrf-token": "csrf" },
|
||||
payload: { providerId: "missing" },
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 202);
|
||||
assert.equal(response.json().data.status, 'PENDING');
|
||||
assert.equal(response.json().data.status, "PENDING");
|
||||
|
||||
const jobResponse = await app.inject({ method: 'GET', url: '/api/v1/admin/jobs/job-1' });
|
||||
const jobResponse = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/v1/admin/jobs/job-1",
|
||||
});
|
||||
assert.equal(jobResponse.statusCode, 200);
|
||||
assert.equal(jobResponse.json().data.status, 'COMPLETED');
|
||||
assert.equal(jobResponse.json().data.status, "COMPLETED");
|
||||
});
|
||||
|
||||
test('returns a conflict when refresh has no active product mapping', async (testContext) => {
|
||||
test("returns a conflict when refresh has no active product mapping", async (testContext) => {
|
||||
const app = buildApp({
|
||||
authenticateAdmin: async () => ({ sellerId: 'seller-1' }),
|
||||
authenticateAdmin: async () => ({ sellerId: "seller-1" }),
|
||||
authorizeUnsafeRequest: async () => {},
|
||||
pricingService: {
|
||||
async enqueueRefresh() {
|
||||
const error = new Error('No active pricing provider mapping exists for this product.');
|
||||
error.code = 'PRICING_PROVIDER_MAPPING_NOT_FOUND';
|
||||
const error = new Error(
|
||||
"No active pricing provider mapping exists for this product.",
|
||||
);
|
||||
error.code = "PRICING_PROVIDER_MAPPING_NOT_FOUND";
|
||||
error.statusCode = 409;
|
||||
throw error;
|
||||
},
|
||||
@@ -88,30 +132,53 @@ test('returns a conflict when refresh has no active product mapping', async (tes
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/admin/inventory/item-1/pricing/refresh',
|
||||
headers: { 'x-csrf-token': 'csrf' },
|
||||
payload: { productId: 'product-1', conditionId: 'condition-1' },
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/inventory/item-1/pricing/refresh",
|
||||
headers: { "x-csrf-token": "csrf" },
|
||||
payload: { productId: "product-1", conditionId: "condition-1" },
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 409);
|
||||
assert.equal(response.json().error.code, 'PRICING_PROVIDER_MAPPING_NOT_FOUND');
|
||||
assert.equal(
|
||||
response.json().error.code,
|
||||
"PRICING_PROVIDER_MAPPING_NOT_FOUND",
|
||||
);
|
||||
});
|
||||
|
||||
test('lists jobs and retries a failed job with CSRF protection', async (testContext) => {
|
||||
test("lists jobs and retries a failed job with CSRF protection", async (testContext) => {
|
||||
let csrfChecks = 0;
|
||||
const app = buildApp({
|
||||
authenticateAdmin: async () => ({ sellerId: 'seller-1' }),
|
||||
authorizeUnsafeRequest: async () => { csrfChecks += 1; },
|
||||
authenticateAdmin: async () => ({ sellerId: "seller-1" }),
|
||||
authorizeUnsafeRequest: async () => {
|
||||
csrfChecks += 1;
|
||||
},
|
||||
pricingService: {
|
||||
async listJobs() { return [{ id: 'job-1', status: 'FAILED', providerId: 'fixture', inventoryItemId: 'item-1' }]; },
|
||||
async retryJob() { return { id: 'job-1', status: 'PENDING' }; },
|
||||
async listJobs() {
|
||||
return [
|
||||
{
|
||||
id: "job-1",
|
||||
status: "FAILED",
|
||||
providerId: "fixture",
|
||||
inventoryItemId: "item-1",
|
||||
},
|
||||
];
|
||||
},
|
||||
async retryJob() {
|
||||
return { id: "job-1", status: "PENDING" };
|
||||
},
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
const listResponse = await app.inject({ method: 'GET', url: '/api/v1/admin/jobs' });
|
||||
const retryResponse = await app.inject({ method: 'POST', url: '/api/v1/admin/jobs/job-1/retry', headers: { 'x-csrf-token': 'csrf' } });
|
||||
const listResponse = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/v1/admin/jobs",
|
||||
});
|
||||
const retryResponse = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/jobs/job-1/retry",
|
||||
headers: { "x-csrf-token": "csrf" },
|
||||
});
|
||||
assert.equal(listResponse.statusCode, 200);
|
||||
assert.equal(retryResponse.statusCode, 202);
|
||||
assert.equal(csrfChecks, 1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildApp } from '../../src/app/build-app.js';
|
||||
import { buildApp } from "../../src/app/build-app.js";
|
||||
|
||||
test('protects provider mapping writes and scopes mappings to the seller', async (testContext) => {
|
||||
test("protects provider mapping writes and scopes mappings to the seller", async (testContext) => {
|
||||
let received;
|
||||
const app = buildApp({
|
||||
authenticateAdmin: async () => ({ sellerId: 'seller-1' }),
|
||||
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' }; },
|
||||
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' },
|
||||
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');
|
||||
assert.equal(received.sellerId, "seller-1");
|
||||
assert.equal(response.json().data.providerId, "fixture");
|
||||
});
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildApp } from '../../src/app/build-app.js';
|
||||
import { buildApp } from "../../src/app/build-app.js";
|
||||
|
||||
test('returns public search results with pagination metadata', async (testContext) => {
|
||||
test("returns public search results with pagination metadata", async (testContext) => {
|
||||
const app = buildApp({
|
||||
searchService: {
|
||||
async searchPublic(filters) {
|
||||
assert.equal(filters.page, 2);
|
||||
assert.equal(filters.limit, 5);
|
||||
assert.equal(filters.includeSold, true);
|
||||
assert.equal(filters.sort, 'price_low');
|
||||
assert.equal(filters.conditionId, 'condition-1');
|
||||
assert.equal(filters.sort, "price_low");
|
||||
assert.equal(filters.conditionId, "condition-1");
|
||||
return {
|
||||
items: [{ id: 'listing-1', status: 'CLOSED' }],
|
||||
items: [{ id: "listing-1", status: "CLOSED" }],
|
||||
pagination: { page: 2, limit: 5, total: 6, totalPages: 2 },
|
||||
};
|
||||
},
|
||||
@@ -22,8 +22,8 @@ test('returns public search results with pagination metadata', async (testContex
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/v1/public/search?query=charizard&page=2&limit=5&includeSold=true&sort=price_low&conditionId=condition-1',
|
||||
method: "GET",
|
||||
url: "/api/v1/public/search?query=charizard&page=2&limit=5&includeSold=true&sort=price_low&conditionId=condition-1",
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
@@ -33,44 +33,71 @@ test('returns public search results with pagination metadata', async (testContex
|
||||
total: 6,
|
||||
totalPages: 2,
|
||||
});
|
||||
assert.equal(response.json().data.items[0].id, 'listing-1');
|
||||
assert.equal(response.json().data.items[0].id, "listing-1");
|
||||
});
|
||||
|
||||
test('rejects malformed numeric search filters', async (testContext) => {
|
||||
const app = buildApp({ searchService: { async searchPublic() { throw new Error('should not run'); } } });
|
||||
test("rejects malformed numeric search filters", async (testContext) => {
|
||||
const app = buildApp({
|
||||
searchService: {
|
||||
async searchPublic() {
|
||||
throw new Error("should not run");
|
||||
},
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/v1/public/search?minPriceMinor=12.5',
|
||||
method: "GET",
|
||||
url: "/api/v1/public/search?minPriceMinor=12.5",
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 400);
|
||||
assert.equal(response.json().error.code, 'INVALID_SEARCH_FILTER');
|
||||
assert.equal(response.json().error.code, "INVALID_SEARCH_FILTER");
|
||||
});
|
||||
|
||||
test('returns active public category options', async (testContext) => {
|
||||
test("returns active public category options", async (testContext) => {
|
||||
const app = buildApp({
|
||||
catalogLookupRepository: {
|
||||
async listCategories() { return [{ id: 'category-1', name: 'Trading Cards' }]; },
|
||||
async listCategories() {
|
||||
return [{ id: "category-1", name: "Trading Cards" }];
|
||||
},
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({ method: 'GET', url: '/api/v1/public/lookups/categories' });
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/v1/public/lookups/categories",
|
||||
});
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.deepEqual(response.json().data, [{ id: 'category-1', name: 'Trading Cards' }]);
|
||||
assert.deepEqual(response.json().data, [
|
||||
{ id: "category-1", name: "Trading Cards" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns public condition options without category internals', async (testContext) => {
|
||||
test("returns public condition options without category internals", async (testContext) => {
|
||||
const app = buildApp({
|
||||
catalogLookupRepository: {
|
||||
async listConditions() { return [{ id: 'condition-1', name: 'Near Mint', code: 'NM', categoryId: 'private-category' }]; },
|
||||
async listConditions() {
|
||||
return [
|
||||
{
|
||||
id: "condition-1",
|
||||
name: "Near Mint",
|
||||
code: "NM",
|
||||
categoryId: "private-category",
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({ method: 'GET', url: '/api/v1/public/lookups/conditions' });
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/v1/public/lookups/conditions",
|
||||
});
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.deepEqual(response.json().data, [{ id: 'condition-1', name: 'Near Mint', code: 'NM' }]);
|
||||
});
|
||||
assert.deepEqual(response.json().data, [
|
||||
{ id: "condition-1", name: "Near Mint", code: "NM" },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildApp } from '../../src/app/build-app.js';
|
||||
import { buildApp } from "../../src/app/build-app.js";
|
||||
|
||||
const saleResult = {
|
||||
sale: {
|
||||
id: 'sale-1',
|
||||
sellerId: 'seller-1',
|
||||
saleDate: '2026-09-09',
|
||||
id: "sale-1",
|
||||
sellerId: "seller-1",
|
||||
saleDate: "2026-09-09",
|
||||
soldPriceMinor: 2499,
|
||||
currency: 'USD',
|
||||
currency: "USD",
|
||||
platformFeesMinor: 0,
|
||||
paymentProcessingFeesMinor: 0,
|
||||
shippingCostMinor: 0,
|
||||
@@ -17,12 +17,12 @@ const saleResult = {
|
||||
otherFeesMinor: 0,
|
||||
},
|
||||
saleItem: {
|
||||
id: 'sale-item-1',
|
||||
inventoryItemId: 'inventory-1',
|
||||
id: "sale-item-1",
|
||||
inventoryItemId: "inventory-1",
|
||||
soldPriceMinor: 2499,
|
||||
costBasisMinor: 1200,
|
||||
},
|
||||
inventoryItem: { id: 'inventory-1', status: 'SOLD' },
|
||||
inventoryItem: { id: "inventory-1", status: "SOLD" },
|
||||
};
|
||||
|
||||
function createTestApp({ authenticateAdmin, authorizeUnsafeRequest } = {}) {
|
||||
@@ -31,70 +31,74 @@ function createTestApp({ authenticateAdmin, authorizeUnsafeRequest } = {}) {
|
||||
authorizeUnsafeRequest,
|
||||
saleService: {
|
||||
async create(input) {
|
||||
assert.equal(input.sellerId, 'seller-1');
|
||||
assert.equal(input.sellerId, "seller-1");
|
||||
return saleResult;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
test('creates a sale for the authenticated seller with CSRF protection', async (testContext) => {
|
||||
test("creates a sale for the authenticated seller with CSRF protection", async (testContext) => {
|
||||
let csrfChecks = 0;
|
||||
const app = createTestApp({
|
||||
authenticateAdmin: async () => ({ sellerId: 'seller-1' }),
|
||||
authorizeUnsafeRequest: async () => { csrfChecks += 1; },
|
||||
authenticateAdmin: async () => ({ sellerId: "seller-1" }),
|
||||
authorizeUnsafeRequest: async () => {
|
||||
csrfChecks += 1;
|
||||
},
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/admin/sales',
|
||||
headers: { 'x-csrf-token': 'csrf-token' },
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/sales",
|
||||
headers: { "x-csrf-token": "csrf-token" },
|
||||
payload: {
|
||||
inventoryItemId: 'd2f67d74-9647-48d5-b3a4-74d3b16ea9f8',
|
||||
inventoryItemId: "d2f67d74-9647-48d5-b3a4-74d3b16ea9f8",
|
||||
costBasisMinor: 1200,
|
||||
saleDate: '2026-09-09',
|
||||
saleDate: "2026-09-09",
|
||||
soldPriceMinor: 2499,
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 201);
|
||||
assert.equal(response.json().data.inventoryItem.status, 'SOLD');
|
||||
assert.equal(response.json().data.inventoryItem.status, "SOLD");
|
||||
assert.equal(csrfChecks, 1);
|
||||
});
|
||||
|
||||
test('rejects invalid sale requests and unauthenticated requests', async (testContext) => {
|
||||
const app = createTestApp({ authenticateAdmin: async () => ({ sellerId: 'seller-1' }) });
|
||||
test("rejects invalid sale requests and unauthenticated requests", async (testContext) => {
|
||||
const app = createTestApp({
|
||||
authenticateAdmin: async () => ({ sellerId: "seller-1" }),
|
||||
});
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const invalidResponse = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/admin/sales',
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/sales",
|
||||
payload: {
|
||||
inventoryItemId: 'not-a-uuid',
|
||||
inventoryItemId: "not-a-uuid",
|
||||
costBasisMinor: -1,
|
||||
saleDate: 'tomorrow',
|
||||
saleDate: "tomorrow",
|
||||
soldPriceMinor: 0,
|
||||
},
|
||||
});
|
||||
assert.equal(invalidResponse.statusCode, 400);
|
||||
assert.equal(invalidResponse.json().error.code, 'VALIDATION_ERROR');
|
||||
assert.equal(invalidResponse.json().error.code, "VALIDATION_ERROR");
|
||||
|
||||
const unauthenticatedApp = createTestApp();
|
||||
testContext.after(() => unauthenticatedApp.close());
|
||||
const unauthenticatedResponse = await unauthenticatedApp.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/admin/sales',
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/sales",
|
||||
payload: {},
|
||||
});
|
||||
assert.equal(unauthenticatedResponse.statusCode, 401);
|
||||
assert.equal(unauthenticatedResponse.json().error.code, 'UNAUTHORIZED');
|
||||
assert.equal(unauthenticatedResponse.json().error.code, "UNAUTHORIZED");
|
||||
});
|
||||
|
||||
test('voids a sale with CSRF protection and actor identity', async (testContext) => {
|
||||
test("voids a sale with CSRF protection and actor identity", async (testContext) => {
|
||||
let voidInput;
|
||||
const app = buildApp({
|
||||
authenticateAdmin: async () => ({ userId: 'user-1', sellerId: 'seller-1' }),
|
||||
authenticateAdmin: async () => ({ userId: "user-1", sellerId: "seller-1" }),
|
||||
authorizeUnsafeRequest: async () => {},
|
||||
saleService: {
|
||||
async void(input) {
|
||||
@@ -106,13 +110,13 @@ test('voids a sale with CSRF protection and actor identity', async (testContext)
|
||||
testContext.after(() => app.close());
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/admin/sales/sale-1/void',
|
||||
headers: { 'x-csrf-token': 'csrf-token' },
|
||||
payload: { reason: 'Duplicate entry' },
|
||||
method: "POST",
|
||||
url: "/api/v1/admin/sales/sale-1/void",
|
||||
headers: { "x-csrf-token": "csrf-token" },
|
||||
payload: { reason: "Duplicate entry" },
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(voidInput.actorUserId, 'user-1');
|
||||
assert.equal(response.json().data.inventoryItem.status, 'SOLD');
|
||||
});
|
||||
assert.equal(voidInput.actorUserId, "user-1");
|
||||
assert.equal(response.json().data.inventoryItem.status, "SOLD");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user