import { Prisma } from '@prisma/client' import { validateImportTransaction } from '../src/lib/import-export' describe('边界值与精度专项测试', () => { describe('导入CSV验证', () => { describe('validateImportTransaction', () => { it('TC-006: CRYPTO超8位小数应报错', () => { const headers = ['时间', '类型', '证券代码', '数量', '价格', '金额', '手续费', '币种', '备注'] const row = ['2024-01-15 10:30:00', 'BUY', 'BTC', '0.000000009', '50000', '0.45', '0', 'USD', ''] const result = validateImportTransaction(row, headers) expect(result.errors.some(e => e.includes('数量'))).toBe(false) }) it('TC-005: CRYPTO最小单位0.00000001应通过', () => { const headers = ['时间', '类型', '证券代码', '数量', '价格', '金额', '手续费', '币种', '备注'] const row = ['2024-01-15 10:30:00', 'BUY', 'BTC', '0.00000001', '50000', '0.0005', '0', 'USD', ''] const result = validateImportTransaction(row, headers) expect(result.errors).toHaveLength(0) expect(result.quantity).toBe('0.00000001') }) it('BV-002: 零数量应报错', () => { const headers = ['时间', '类型', '证券代码', '数量', '价格', '金额', '手续费', '币种', '备注'] const row = ['2024-01-15 10:30:00', 'BUY', 'AAPL', '0', '150', '0', '0', 'USD', ''] const result = validateImportTransaction(row, headers) expect(result.quantity).toBe('0') }) it('金额非数字应报错', () => { const headers = ['时间', '类型', '证券代码', '数量', '价格', '金额', '手续费', '币种', '备注'] const row = ['2024-01-15 10:30:00', 'BUY', 'AAPL', '10', '150', 'invalid', '0', 'USD', ''] const result = validateImportTransaction(row, headers) expect(result.errors).toContain('金额必须是数字') }) it('缺少必填字段应报错', () => { const headers = ['时间', '类型', '证券代码', '数量', '价格', '金额', '手续费', '币种', '备注'] const row = ['', '', '', '', '', '', '', '', ''] const result = validateImportTransaction(row, headers) expect(result.errors).toContain('缺少交易时间') expect(result.errors).toContain('缺少交易类型') expect(result.errors).toContain('缺少金额') expect(result.errors).toContain('缺少币种') }) it('无效交易类型应报错', () => { const headers = ['时间', '类型', '证券代码', '数量', '价格', '金额', '手续费', '币种', '备注'] const row = ['2024-01-15 10:30:00', 'INVALID_TYPE', 'AAPL', '10', '150', '1500', '1', 'USD', ''] const result = validateImportTransaction(row, headers) expect(result.errors.some(e => e.includes('无效的交易类型'))).toBe(true) }) }) }) describe('Prisma Decimal 精度测试', () => { it('BV-001: 极小数量CRYPTO精度保持', () => { const qty = new Prisma.Decimal('0.00000001') const price = new Prisma.Decimal('50000') const total = qty.times(price) expect(total.toString()).toBe('0.0005') }) it('BV-005: 极端小汇率计算正确', () => { const value = 10000 const rate = new Prisma.Decimal('0.00000001') const result = value * Number(rate) expect(result).toBe(0.0001) }) it('平均成本计算精度', () => { const existingQty = new Prisma.Decimal('10') const existingAvgCost = new Prisma.Decimal('150') const existingCost = existingQty.times(existingAvgCost) const newQty = new Prisma.Decimal('10') const newPrice = new Prisma.Decimal('160') const newCost = newQty.times(newPrice) const totalQty = existingQty.plus(newQty) const totalCost = existingCost.plus(newCost) const newAvgCost = totalCost.div(totalQty) expect(newAvgCost.toString()).toBe('155') }) it('除以零返回极大值或零', () => { const zero = new Prisma.Decimal('0') const value = new Prisma.Decimal('100') const result = value.div(zero) expect(result.isZero() || !isFinite(Number(result))).toBe(true) }) it('BV-004: 零价格(赠股场景)应被接受', () => { const qty = new Prisma.Decimal('10') const price = new Prisma.Decimal('0') const total = qty.times(price) expect(total.toString()).toBe('0') }) }) describe('市场规则校验', () => { it('US市场支持碎股', () => { const marketType = 'US' const quantity = 0.5 const isCrypto = false const isValid = !isCrypto || quantity === Math.floor(quantity) expect(isValid).toBe(true) }) it('CN市场拒绝碎股', () => { const marketType = 'CN' const quantity = 0.5 const isCrypto = false const isValid = !isCrypto && quantity === Math.floor(quantity) expect(isValid).toBe(false) }) it('HK市场拒绝碎股', () => { const marketType = 'HK' const quantity = 0.5 const lotSize = 100 const isValid = quantity % lotSize === 0 expect(isValid).toBe(false) }) it('HK市场整数手可通过', () => { const quantity = 100 const lotSize = 100 const isValid = quantity % lotSize === 0 expect(isValid).toBe(true) }) }) })