71 lines
3.7 KiB
JavaScript
71 lines
3.7 KiB
JavaScript
'use strict';
|
|
require('reflect-metadata');
|
|
const assert = require('node:assert/strict');
|
|
const test = require('node:test');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const ts = require('typescript');
|
|
const { SaasBusinessReject } = require('@cool-midway/module-runtime');
|
|
const sourceRoot = path.resolve(__dirname, '../src');
|
|
const previous = require.extensions['.ts'];
|
|
let GoodsService;
|
|
try {
|
|
require.extensions['.ts'] = (module, filename) => {
|
|
assert.ok(filename.startsWith(sourceRoot + path.sep));
|
|
const compiled = ts.transpileModule(fs.readFileSync(filename, 'utf8'), {
|
|
fileName: filename,
|
|
compilerOptions: { target: ts.ScriptTarget.ES2018, module: ts.ModuleKind.CommonJS,
|
|
experimentalDecorators: true, emitDecoratorMetadata: true, esModuleInterop: true },
|
|
});
|
|
module._compile(compiled.outputText, filename);
|
|
};
|
|
({ GoodsService } = require('../src/service/goods.ts'));
|
|
} finally {
|
|
if (previous) require.extensions['.ts'] = previous;
|
|
else delete require.extensions['.ts'];
|
|
}
|
|
function fixture(context = {}) {
|
|
const service = new GoodsService();
|
|
const calls = [];
|
|
service.saasActionContext = context;
|
|
service.inventory = { assertNoActiveReservations: async id => { calls.push(id); } };
|
|
return { service, calls };
|
|
}
|
|
const rejects = code => error => error instanceof SaasBusinessReject && error.code === code;
|
|
|
|
test('new catalog records accept an explicit initial stock and nonnegative decimal price', async () => {
|
|
const { service, calls } = fixture();
|
|
await service.modifyBefore({ operation: 'add', input: { title: '验收商品', price: '12.50', stock: 20, status: 1 } });
|
|
await service.modifyBefore({ operation: 'add', input: { title: '赠品', price: '0.00', stock: 0, status: 0 } });
|
|
assert.deepEqual(calls, []);
|
|
});
|
|
|
|
test('invalid catalog values are business rejections before persistence', async t => {
|
|
const inputs = [{ title: ' ' }, { price: '-0.01' }, { stock: -1 }, { stock: 1.5 }, { stock: 2147483648 }, { status: 2 }];
|
|
for (const input of inputs) await t.test(JSON.stringify(input), async () => {
|
|
const { service } = fixture();
|
|
await assert.rejects(service.modifyBefore({ operation: 'add', input: { title: '商品', price: '1.00', ...input } }), rejects('GOODS_INPUT_INVALID'));
|
|
});
|
|
});
|
|
|
|
test('catalog edits keep managed inventory protected even with valid stock values', async () => {
|
|
const { service } = fixture();
|
|
await service.modifyBefore({ operation: 'update', input: { id: '1', title: '更新名称', price: '0.00', status: 0 } });
|
|
await assert.rejects(service.modifyBefore({ operation: 'update', input: { id: '1', stock: 2 } }), rejects('STOCK_COMMAND_REQUIRED'));
|
|
await assert.rejects(service.modifyBefore({ operation: 'update', input: { id: '1', price: '-1.00' } }), rejects('GOODS_INPUT_INVALID'));
|
|
});
|
|
|
|
test('managed stock updates retain the same domain validation', async () => {
|
|
const { service } = fixture({ managedCrudCaller: { kind: 'business', actionName: 'goods.reserveStock' }, workflow: { coordinatorModule: 'order' } });
|
|
await service.modifyBefore({ operation: 'update', input: { id: '1', stock: 0 } });
|
|
await assert.rejects(service.modifyBefore({ operation: 'update', input: { id: '1', stock: -1 } }), rejects('GOODS_INPUT_INVALID'));
|
|
});
|
|
|
|
test('deletion still requires the inventory reservation lock and rejects an active reservation', async () => {
|
|
const { service, calls } = fixture();
|
|
await service.modifyBefore({ operation: 'delete', input: { id: '1' } });
|
|
assert.deepEqual(calls, ['1']);
|
|
service.inventory.assertNoActiveReservations = async () => { throw new SaasBusinessReject('STOCK_RESERVATION_CONFLICT', '仍有库存预留'); };
|
|
await assert.rejects(service.modifyBefore({ operation: 'delete', input: { id: '1' } }), rejects('STOCK_RESERVATION_CONFLICT'));
|
|
});
|