Remove the temporary listing-level image count from the schema, validation, DTO, and draft contract. Publication now requires the persisted inventory image repository count. Add migration 0009 and update listing tests and implementation status. Verification: npm test passed 62 tests and integration passed 14 tests. No build script is configured in this repository.
164 lines
4.6 KiB
JavaScript
164 lines
4.6 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import { createListingService } from '../../src/modules/listings/services/listing-service.js';
|
|
|
|
test('creates a draft listing only after the listing publish requirements are met', async () => {
|
|
const created = [];
|
|
const service = createListingService({
|
|
listingRepository: {
|
|
async create(listing) {
|
|
const record = { id: 'listing-1', ...listing };
|
|
created.push(record);
|
|
return record;
|
|
},
|
|
},
|
|
inventoryImageRepository: {
|
|
async countByInventoryItemId() {
|
|
return 2;
|
|
},
|
|
},
|
|
});
|
|
|
|
const listing = await service.create({
|
|
inventoryItemId: 'inventory-item-id',
|
|
productName: ' Charizard ex ',
|
|
categoryId: 'category-id',
|
|
conditionId: 'condition-id',
|
|
askingPriceMinor: 2499,
|
|
notes: ' Near mint ',
|
|
});
|
|
|
|
assert.deepEqual(listing, {
|
|
id: 'listing-1',
|
|
inventoryItemId: 'inventory-item-id',
|
|
productName: 'Charizard ex',
|
|
categoryId: 'category-id',
|
|
conditionId: 'condition-id',
|
|
askingPriceMinor: 2499,
|
|
status: 'DRAFT',
|
|
notes: 'Near mint',
|
|
});
|
|
|
|
await assert.rejects(
|
|
() =>
|
|
service.create({
|
|
inventoryItemId: 'inventory-item-id',
|
|
productName: 'Charizard ex',
|
|
categoryId: 'category-id',
|
|
conditionId: 'condition-id',
|
|
askingPriceMinor: 0,
|
|
}),
|
|
/Asking price must be a positive integer minor unit/,
|
|
);
|
|
|
|
assert.equal(created.length, 1);
|
|
});
|
|
|
|
test('publishes an owned draft only when its persisted requirements are met', async () => {
|
|
const updated = [];
|
|
const service = createListingService({
|
|
listingRepository: {
|
|
async findByIdForSeller() {
|
|
return {
|
|
id: 'listing-1',
|
|
inventoryItemId: 'inventory-item-id',
|
|
productName: 'Charizard ex',
|
|
categoryId: 'category-id',
|
|
conditionId: 'condition-id',
|
|
askingPriceMinor: 2499,
|
|
status: 'DRAFT',
|
|
};
|
|
},
|
|
async updateStatus(id, status) {
|
|
updated.push({ id, status });
|
|
return { id, status };
|
|
},
|
|
},
|
|
inventoryImageRepository: {
|
|
async countByInventoryItemId() {
|
|
return 2;
|
|
},
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(
|
|
await service.transition({
|
|
listingId: 'listing-1',
|
|
sellerId: 'seller-1',
|
|
nextStatus: 'AVAILABLE',
|
|
}),
|
|
{ id: 'listing-1', status: 'AVAILABLE' },
|
|
);
|
|
assert.deepEqual(updated, [{ id: 'listing-1', status: 'AVAILABLE' }]);
|
|
});
|
|
|
|
test('does not transition a listing owned by another seller', async () => {
|
|
const service = createListingService({
|
|
listingRepository: {
|
|
async findByIdForSeller() {
|
|
return null;
|
|
},
|
|
},
|
|
});
|
|
|
|
await assert.rejects(
|
|
() => service.transition({ listingId: 'listing-1', sellerId: 'seller-2', nextStatus: 'AVAILABLE' }),
|
|
(error) => error.code === 'LISTING_NOT_FOUND' && error.statusCode === 404,
|
|
);
|
|
});
|
|
|
|
test('does not publish a listing for sold or archived inventory', async () => {
|
|
for (const inventoryStatus of ['SOLD', 'ARCHIVED']) {
|
|
const service = createListingService({
|
|
listingRepository: {
|
|
async findByIdForSeller() {
|
|
return {
|
|
id: 'listing-1',
|
|
inventoryItemId: 'inventory-item-id',
|
|
productName: 'Charizard ex',
|
|
categoryId: 'category-id',
|
|
conditionId: 'condition-id',
|
|
askingPriceMinor: 2499,
|
|
inventoryStatus,
|
|
status: 'DRAFT',
|
|
};
|
|
},
|
|
async updateStatus() {
|
|
throw new Error('A blocked listing must not be updated.');
|
|
},
|
|
},
|
|
inventoryImageRepository: {
|
|
async countByInventoryItemId() {
|
|
return 2;
|
|
},
|
|
},
|
|
});
|
|
|
|
await assert.rejects(
|
|
() => service.transition({ listingId: 'listing-1', sellerId: 'seller-1', nextStatus: 'AVAILABLE' }),
|
|
(error) => error.code === 'LISTING_INVENTORY_UNAVAILABLE' && error.statusCode === 409,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('lists and retrieves only seller-owned listings', async () => {
|
|
const ownedListing = { id: 'listing-1', status: 'DRAFT' };
|
|
const service = createListingService({
|
|
listingRepository: {
|
|
async listBySellerId(sellerId) {
|
|
assert.equal(sellerId, 'seller-1');
|
|
return [ownedListing];
|
|
},
|
|
async findByIdForSeller(listingId, sellerId) {
|
|
assert.equal(listingId, 'listing-1');
|
|
assert.equal(sellerId, 'seller-1');
|
|
return ownedListing;
|
|
},
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(await service.list({ sellerId: 'seller-1' }), [ownedListing]);
|
|
assert.deepEqual(await service.get({ listingId: 'listing-1', sellerId: 'seller-1' }), ownedListing);
|
|
});
|