import assert from 'node:assert/strict' import test from 'node:test' import { CSV_BOM, buildProductRuleExportRows, buildProductRulesCsv, buildProductRulesExportFilename, describeRuleMapping, downloadProductRulesCsv, downloadTextFile, formatRuleCollectFields, resolveRuleCategoryPath, } from '../src/utils/product-rules-export.ts' function createRule(overrides: Record = {}) { return { ruleId: 1, ruleKey: 'android-暗潮-1钻', provider: '安卓', platform: '快手', shopId: '', skuCode: 'SKU-001', productName: '暗潮1钻', matchType: 'contains', match: { sellerId: '', itemId: '', relItemId: '', skuId: '', relSkuId: '', itemTitle: '', skuNick: '', itemTitleMatchType: 'contains', skuNickMatchType: 'contains', }, categoryId: 20, categoryName: '暗潮', enabled: true, autoCreate: true, rewardAmount: 12000, unitPriceFen: 0, sharing: { enabled: false, totalQuantity: 1, unitReward: 0, autoFromOrder: false }, requirement: { fields: [] }, timeoutMinutes: 60, timeoutPolicy: 'reopen', acceptanceMode: 'standard', sortOrder: 1, createdAt: '2026-02-21T10:00:00.000Z', updatedAt: '2026-02-21T11:00:00.000Z', ...overrides, } as never } test('接单模板导出:分类展示成「一级 / 二级」', () => { const categoryMap = new Map([ [10, { categoryId: 10, name: '三角洲行动', parentId: null, sortOrder: 1 }], [20, { categoryId: 20, name: '暗潮', parentId: 10, sortOrder: 1 }], ]) as never assert.equal(resolveRuleCategoryPath(createRule(), categoryMap), '三角洲行动 / 暗潮') }) test('接单模板导出:分类缺失时回退未分类,不再显示空列', () => { assert.equal( resolveRuleCategoryPath(createRule({ categoryId: null, categoryName: '' })), '未分类', ) }) test('接单模板导出:金额按元输出,随订单计算的模板明确标注', () => { const [fixed] = buildProductRuleExportRows( [createRule({ unitPriceFen: 5000 })], new Map(), new Map(), ) assert.equal(fixed.rewardAmountYuan, '120.00') assert.equal(fixed.unitPriceYuan, '50.00') const [autoFromOrder] = buildProductRuleExportRows( [ createRule({ rewardAmount: 0, sharing: { enabled: true, autoFromOrder: true, totalQuantity: 5, unitReward: 2000 }, }), ], new Map(), new Map(), ) assert.equal(autoFromOrder.rewardAmountYuan, '随订单计算') assert.equal(autoFromOrder.sharingEnabled, true) assert.equal(autoFromOrder.sharingTotalQuantity, 5) assert.equal(autoFromOrder.sharingUnitRewardYuan, '20.00') }) test('接单模板导出:商品绑定汇总带店铺、商品与启用状态', () => { const row = { mappingId: 1, ruleId: 1, ruleKey: 'android-暗潮-1钻', productName: '暗潮1钻', sellerIds: ['11300001'], sellerId: '11300001', relItemId: '', itemTitle: '暗潮 1钻 代练', relSkuId: '', skuNick: '', skuMatchMode: 'fuzzy', mappingType: 'product_default', enabled: true, createdAt: '', updatedAt: '', } as never assert.equal(describeRuleMapping(row), '商品默认·店铺[11300001]·商品[暗潮 1钻 代练]·启用') }) test('接单模板导出:CSV 转义逗号、引号与换行', () => { const rule = createRule({ productName: '暗潮,1钻 "特惠"', requirement: { fields: [{ key: 'gameId', label: '游戏\n编号', options: [] }] }, }) const rows = buildProductRuleExportRows([rule], new Map(), new Map()) const csv = buildProductRulesCsv(rows) assert.ok(csv.startsWith('模板名称,模板标识,分类,')) assert.ok(csv.includes('"暗潮,1钻 ""特惠"""')) // 换行被引号包裹后仍是同一行记录 assert.equal(csv.split('\r\n').length, 2) }) test('接单模板导出:资料字段沿用 key:名称#选项 口径', () => { assert.equal( formatRuleCollectFields([ { key: 'gameId', label: '游戏编号', options: [] }, { key: 'system', label: '系统', options: ['安卓', '苹果'] }, ] as never), 'gameId:游戏编号;system:系统#安卓,苹果', ) }) test('接单模板导出:文件名带时间戳且为 .csv', () => { const filename = buildProductRulesExportFilename(new Date(2026, 1, 21, 15, 30, 5)) assert.equal(filename, '接单模板_20260221_1530.csv') assert.equal(CSV_BOM, '\uFEFF') }) test('接单模板导出:空列表只输出表头', () => { const csv = buildProductRulesCsv([]) assert.equal(csv.split('\r\n').length, 1) assert.ok(csv.includes('绑定明细')) }) test('接单模板导出:非浏览器环境不抛错,返回 false 由调用方提示', () => { assert.equal(downloadTextFile('x.csv', 'a,b'), false) assert.equal(downloadProductRulesCsv(buildProductRuleExportRows([], new Map(), new Map())), false) })