'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')); });