import test from 'node:test' import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import ts from 'typescript' import * as vue from 'vue' import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc' const read = (path) => readFileSync(path, 'utf8') function evaluate(source, imports = {}, globals = {}) { const code = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText const m = { exports: {} } new Function('require', 'module', 'exports', ...Object.keys(globals), code)( (name) => { if (name.endsWith('.vue')) return { default: {} } assert.ok(imports[name], `Unexpected dependency: ${name}`) return imports[name] }, m, m.exports, ...Object.values(globals) ) return m.exports } const helpers = evaluate(read('src/utils/business/packageSilence.ts')) const constants = evaluate(read('src/config/constants/packageSilence.ts')) const format = evaluate(read('src/utils/business/format.ts')) const gateway = evaluate(read('src/utils/business/gateway.ts'), { './format': format }) const polling = evaluate(read('src/utils/business/pollingScheduler.ts'), { './gateway': gateway }) const exportConfig = evaluate(read('src/config/constants/exportTask.ts')) test('silence business errors never count as successful orders, package saves or refunds', () => { for (const code of [1260, 1261, 1262, 1268, 1275]) assert.throws(() => helpers.assertSilenceResponse({ code, data: null }), { message: helpers.SILENCE_ERROR_MESSAGES[code] }) assert.throws( () => helpers.assertSilenceResponse({ code: 1266, msg: 'min_days=30,max_days=90' }), { message: 'min_days=30,max_days=90' } ) const response = { code: 0, data: { warnings: ['saved'] } } assert.equal(helpers.assertSilenceResponse(response), response) for (const path of [ 'src/views/order-management/order-list/index.vue', 'src/views/package-management/package-list/index.vue', 'src/views/asset-management/asset-information/components/dialogs/PackageRechargeDialog.vue', 'src/components/business/CreateRefundDialog.vue' ]) assert.ok(read(path).includes('assertSilenceResponse')) }) function setup(path, { type = 2, props = {}, permissions = [], overrides = {} } = {}) { const calls = [], unmount = [] const user = vue.reactive({ info: { id: 1, user_type: type }, get isSuperAdmin() { return Number(this.info.user_type) === 1 }, get getUserInfo() { return this.info } }) const services = new Proxy( {}, { get: (_, name) => async (...args) => { calls.push({ name, args }) return overrides[name] ? overrides[name](...args) : { code: 0, data: { items: [], total: 0 } } } } ) const runtime = { ...vue, onMounted() {}, onActivated() {}, onDeactivated() {}, onBeforeUnmount: (fn) => unmount.push(fn) } const store = { useUserStore: () => user } const auth = { useAuth: () => ({ hasAuth: (code) => user.isSuperAdmin || permissions.includes(code) }) } const scheduler = evaluate(read('src/composables/useSchedulerRequest.ts'), { vue: runtime, '@/store/modules/user': store, '@/utils/business/gateway': gateway, '@/utils/business/pollingScheduler': polling }) const checked = evaluate(read('src/composables/useCheckedColumns.ts'), { vue: runtime }) const imports = { vue: runtime, pinia: { storeToRefs: vue.toRefs }, 'vue-router': { useRouter: () => ({ push() {}, back() {} }) }, 'element-plus': { ElMessage: { success() {}, warning() {}, error() {} }, ElMessageBox: { confirm: async () => {} }, ElTag: {} }, '@/api/modules': { OrderService: services, ExportTaskService: services, CardService: services, DeviceService: services, PackageManageService: services, ShopService: services, AssetService: services }, '@/api/modules/packageSilence': { PackageSilenceService: services }, '@/store/modules/user': store, '@/composables/useAuth': auth, '@/composables/useCheckedColumns': checked, '@/composables/useSchedulerRequest': scheduler, '@/utils/business/format': format, '@/utils/business/packageSilence': helpers, '@/utils/business/pollingScheduler': polling, '@/utils/business/gateway': gateway, '@/utils/business/orderPayment': {}, '@/utils/business': { hasVoucherKeys: (v) => !!v?.length, toVoucherKeyList: (v) => v }, '@/router/routesAlias': { RoutesAlias: {} }, '@/config/constants/audit': { AUDIT_PERMISSIONS: {} }, '@/utils/business/auditNavigation': {}, '@/components/business/audit/investigationController': {}, '@/config/constants/bulkPurchase': { BULK_PURCHASE_PERMISSIONS: {} }, '@/config/constants/packageSilence': constants, '@/config/constants': exportConfig, '@/config/constants/exportTask': exportConfig } const { descriptor } = parse(read(path)), scope = vue.effectScope() const component = evaluate( compileScript(descriptor, { id: 'silence-integration' }).content, imports, runtime ).default const state = scope.run(() => component.setup(props, { expose() {}, emit() {} })) return { state, calls, user, dispose() { unmount.forEach((fn) => fn()) scope.stop() } } } test('order category role matrix and export scene boundaries match the document', () => { for (const type of [1, 2, 3, 4, 0, undefined]) for (const category of ['all', 'platform', 'agent', 'online']) { const allowed = (category === 'online' ? [1, 2] : [1, 2, 3]).includes(type) assert.equal(helpers.canAccessOrderCategory(type, category), allowed) assert.equal( exportConfig.canAccessExportTaskScene( constants.ORDER_CATEGORY_CONFIG[category].scene, type ), allowed ) } assert.equal(helpers.canEditRestrictedAsset(4), false) assert.equal(helpers.canEditRestrictedAsset(3), true) }) test('three category endpoints preserve query and shared legacy detail contract', async () => { const calls = [] class BaseService {} for (const name of ['get', 'getOne']) BaseService[name] = (url, params) => { calls.push({ url, params }) return Promise.resolve({ code: 0 }) } const { OrderService: S } = evaluate(read('src/api/modules/order.ts'), { '../BaseService': { BaseService } }) const query = { page: 1, page_size: 20, is_expired: false, seller_shop_id: 8 } await S.getPlatformOrders(query) await S.getAgentOrders(query) await S.getOnlineOrders(query) await S.getOrders(query) await S.getOrderById(1) assert.deepEqual( calls.map((x) => x.url), [ '/api/admin/orders/platform', '/api/admin/orders/agent', '/api/admin/orders/online', '/api/admin/orders', '/api/admin/orders/1' ] ) for (const call of calls.slice(0, 4)) { assert.deepEqual(call.params, query) assert.equal('category' in call.params, false) } }) for (const [category, method] of [ ['platform', 'getPlatformOrders'], ['agent', 'getAgentOrders'], ['online', 'getOnlineOrders'], ['all', 'getOrders'] ]) test(`order view ${category} uses exact service and scene, blocks disallowed identities`, async () => { for (const type of [1, 2, 3, 4]) { const h = setup('src/views/order-management/order-list/index.vue', { type, props: { category } }) h.state.searchForm.is_expired = 0 await h.state.getTableData() const allowed = helpers.canAccessOrderCategory(type, category) assert.equal(h.calls.length, allowed ? 1 : 0) if (allowed) { assert.equal(h.calls[0].name, method) assert.equal(h.calls[0].args[0].is_expired, false) assert.equal('category' in h.calls[0].args[0], false) } assert.equal( h.state.categoryConfig.value.scene, constants.ORDER_CATEGORY_CONFIG[category].scene ) h.dispose() } }) test('package defaults enforce 1..90, nullable purchase defaults, addon omission and immutable activation base', () => { for (const days of [0, 91, 1.5, NaN]) assert.throws(() => helpers.packageSilenceFields({ package_type: 'formal', expiry_base: 'from_activation', silence_period_days: days }) ) for (const days of [1, 30, 90]) assert.equal( helpers.packageSilenceFields({ package_type: 'formal', expiry_base: 'from_activation', silence_period_days: days }).silence_period_days, days ) assert.throws(() => helpers.packageSilenceFields({ package_type: 'formal', expiry_base: 'from_activation', silence_period_days: null }) ) assert.equal( helpers.packageSilenceFields({ package_type: 'formal', expiry_base: 'from_purchase', silence_period_days: null, silence_extension_enabled: false }).silence_extension_enabled, false ) assert.deepEqual( helpers.packageSilenceFields({ package_type: 'addon', silence_period_days: 999 }), {} ) assert.throws(() => helpers.packageSilenceFields( { package_type: 'formal', expiry_base: 'from_purchase' }, 'from_activation' ) ) }) test('allocation overrides enforce source defaults before single, batch and series writes', async () => { const packageData = { package_type: 'formal', expiry_base: 'from_purchase', silence_period_days: null } let reads = 0 const validation = evaluate(read('src/api/modules/packageSilenceValidation.ts'), { './packageManage': { PackageManageService: { getPackageDetail: async () => { reads++ return { code: 0, data: { ...packageData } } } } }, '@/utils/business/packageSilence': helpers }) const writes = [] class BaseService {} for (const method of ['post', 'put', 'patch', 'create']) BaseService[method] = (url, data) => { writes.push({ url, data }) return Promise.resolve({ code: 0, data }) } const imports = { '../BaseService': { BaseService }, './packageSilenceValidation': validation } const S = evaluate( read('src/api/modules/shopPackageAllocation.ts'), imports ).ShopPackageAllocationService const G = evaluate(read('src/api/modules/shopSeriesGrant.ts'), imports).ShopSeriesGrantService const calls = [ () => S.createShopPackageAllocation({ package_id: 8, expiry_base_override: 'from_activation' }), () => S.createShopPackageBatchAllocations({ package_ids: [8, 9], expiry_base_override: 'from_activation' }), () => S.updateShopPackageAllocationExpiryBase(1, { expiry_base_override: 'from_activation' }, 8), () => G.createShopSeriesGrant({ packages: [{ package_id: 8 }], expiry_base_override: 'from_activation' }), () => G.manageGrantPackages(1, { packages: [{ package_id: 8 }], expiry_base_override: 'from_activation' }) ] for (const call of calls) await assert.rejects(call, /先配置默认沉默期/) assert.equal(writes.length, 0) packageData.silence_period_days = 30 for (const call of calls) await call() assert.equal(writes.length, 5) packageData.expiry_base = 'from_activation' await assert.rejects( () => S.updateShopPackageAllocationExpiryBase(1, { expiry_base_override: 'from_purchase' }, 8), /不允许覆盖/ ) const previousReads = reads await S.createShopPackageAllocation({ package_id: 8, expiry_base_override: null }) await G.manageGrantPackages(1, { packages: [{ package_id: 8, remove: true }], expiry_base_override: 'from_purchase' }) assert.equal(reads, previousReads) }) test('purchase overrides do not impose a 90-day maximum and never leak unauthorized fields', () => { assert.deepEqual(helpers.silencePurchaseFields(false, 91, 'why'), {}) assert.deepEqual(helpers.silencePurchaseFields(true, null, 'why'), {}) assert.deepEqual(helpers.silencePurchaseFields(true, 365, ' why '), { silence_period_days: 365, silence_reason: 'why' }) for (const days of [0, -1, 1.5, NaN]) assert.throws(() => helpers.silencePurchaseFields(true, days, 'why')) assert.throws(() => helpers.silencePurchaseFields(true, 30, ' ')) }) test('purchase override remains null until explicitly enabled and disabling clears the reason', async () => { const path = 'src/components/business/package-silence/SilencePurchaseFields.vue' const { descriptor } = parse(read(path)) const definition = evaluate(compileScript(descriptor, { id: 'optional-override' }).content, { vue }).default let state const days = vue.ref(null), reason = vue.ref('') const component = { ...definition, setup(props, context) { state = definition.setup(props, context) return () => null } } const renderer = vue.createRenderer({ createComment: () => ({}), insert() {}, remove() {}, parentNode: () => null, nextSibling: () => null }) const app = renderer.createApp({ render: () => vue.h(component, { days: days.value, reason: reason.value, 'onUpdate:days': (v) => (days.value = v), 'onUpdate:reason': (v) => (reason.value = v) }) }) app.mount({}) assert.equal(state.overrideEnabled.value, false) assert.equal(days.value, null) state.overrideEnabled.value = true await vue.nextTick() assert.equal(days.value, 1) days.value = 365 reason.value = 'why' await vue.nextTick() assert.equal(state.overrideEnabled.value, true) state.overrideEnabled.value = false await vue.nextTick() assert.equal(days.value, null) assert.equal(reason.value, '') assert.match(descriptor.template.content, / { const range = ['2026-10-01T00:00:00+08:00', '2026-10-28T23:59:59+08:00'] assert.deepEqual(helpers.firstPurchaseRange(range), { first_purchase_start: '2026-09-30T16:00:00Z', first_purchase_end: '2026-10-28T15:59:59Z' }) assert.deepEqual(helpers.firstPurchaseRange([]), {}) for (const value of [['bad', 'date'], range.toReversed(), [range[0]]]) assert.throws(() => helpers.firstPurchaseRange(value)) }) test('new order exports keep query.filters as an object, preserving zero/false and legacy serialization', () => { for (const scene of ['order_platform', 'order_agent', 'order_online', 'order']) { const filters = { payment_status: 2, is_expired: false, seller_shop_id: 0, start_time: '2026-10-01T00:00:00+08:00' } const h = setup('src/components/business/ExportTaskCreateDialog.vue', { props: { scene, query: { filters }, modelValue: true, defaultFormat: 'xlsx' } }) const query = h.state.getNormalizedQuery() assert.deepEqual(query.filters, scene === 'order' ? JSON.stringify(filters) : filters) h.dispose() } }) test('silence switch uses dedicated endpoints and boolean state, never generic system config', async () => { const calls = [] class BaseService {} for (const method of ['get', 'put']) BaseService[method] = (url, data) => { calls.push({ method, url, data }) return Promise.resolve({ code: 0 }) } const { PackageSilenceService: S } = evaluate(read('src/api/modules/packageSilence.ts'), { '../BaseService': { BaseService } }) await S.getSwitch() await S.setSwitch(false) assert.deepEqual(calls, [ { method: 'get', url: '/api/admin/package-silence/switch', data: undefined }, { method: 'put', url: '/api/admin/package-silence/switch', data: { enabled: false } } ]) }) test('silence switch refuses ordinary accounts and exposes failed prechecks from business errors', async () => { for (const type of [2, 3, 4]) { const h = setup('src/views/settings/package-silence-switch/index.vue', { type }) await h.state.load() await h.state.toggle() assert.equal(h.calls.length, 0) h.dispose() } const failed = [{ code: 'unconfigured', name: 'missing config', passed: false, count: 3 }] const h = setup('src/views/settings/package-silence-switch/index.vue', { type: 1, overrides: { getSwitch: () => ({ code: 0, data: { enabled: false, precheck: failed } }), setSwitch: () => ({ code: 1275, msg: 'check failed', data: { failed_items: failed } }) } }) await h.state.load() await h.state.toggle() assert.equal(h.state.data.value.enabled, false) assert.deepEqual(h.state.failed.value, failed) assert.equal(h.state.error.value, 'check failed') await h.state.load() assert.deepEqual(h.state.failed.value, []) assert.equal(h.state.error.value, '') h.dispose() }) test('all new and changed business templates compile, including conditional root order view', () => { const paths = [ 'src/views/order-management/order-list/index.vue', 'src/views/package-management/package-list/index.vue', 'src/views/settings/package-silence-switch/index.vue', 'src/views/asset-management/asset-information/components/PackageListCard.vue', 'src/views/asset-management/asset-information/components/CurrentPackageCard.vue', 'src/components/business/package-silence/SilenceSummary.vue', 'src/components/business/package-silence/SilencePurchaseFields.vue', 'src/components/business/package-silence/PackageSilenceFields.vue' ] for (const path of paths) { const { descriptor, errors } = parse(read(path)) assert.deepEqual(errors, [], path) const result = compileTemplate({ source: descriptor.template.content, filename: path, id: path, compilerOptions: { expressionPlugins: ['typescript'] } }) assert.deepEqual(result.errors, [], path) } }) test('198 base regressions remain compatible while metadata and zero refunds follow the new contract', () => { assert.equal(Object.keys(helpers.SILENCE_ERROR_MESSAGES).length, 16) for (const type of ['override', 'extend', 'extend_batch', 'offline', 'exception', 'policy']) assert.ok( Object.values(constants.PACKAGE_SILENCE_PERMISSIONS).includes(`package_silence:${type}`) ) assert.match(read('src/types/api/wecom.ts'), /package_silence_approval/) assert.match(read('src/views/settings/wecom/scenes/index.vue'), /沉默期审批/) assert.match(read('src/views/finance/refund/index.vue'), /requested_refund_amount"\s+:min="0"/) assert.match( read('src/views/finance/refund/index.vue'), /row.requested_refund_amount == null \? undefined : fenToYuan/ ) for (const path of [ 'src/views/asset-management/iot-card-management/index.vue', 'src/views/asset-management/device-list/index.vue' ]) assert.ok(read(path).includes('firstPurchaseRange')) })