fix: validate goods input and prepare isolated business acceptance
This commit is contained in:
70
test/goods-business-input.test.cjs
Normal file
70
test/goods-business-input.test.cjs
Normal file
@@ -0,0 +1,70 @@
|
||||
'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'));
|
||||
});
|
||||
Reference in New Issue
Block a user