JewelryMall/server/tests/specDataIO.test.ts
2026-02-14 19:29:15 +08:00

64 lines
2.2 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import * as fc from 'fast-check'
import { parseCSV, generateCSV } from '../src/controllers/specDataIO'
// CSV column headers matching spec_data table fields
const CSV_HEADERS = [
'model_name', 'fineness', 'main_stone', 'ring_size',
'gold_total_weight', 'gold_net_weight', 'loss', 'gold_loss',
'gold_price', 'gold_value',
'main_stone_count', 'main_stone_weight', 'main_stone_unit_price', 'main_stone_amount',
'side_stone_count', 'side_stone_weight', 'side_stone_unit_price', 'side_stone_amount',
'accessory_amount', 'processing_fee', 'setting_fee', 'total_labor_cost', 'total_price',
]
const STRING_FIELDS = ['model_name', 'fineness', 'main_stone', 'ring_size']
// Generate a safe string that won't break CSV parsing (no newlines)
const safeStringArb = fc.stringOf(
fc.char().filter((c) => c !== '\n' && c !== '\r'),
{ minLength: 0, maxLength: 20 }
)
// Generate a valid spec data row
const specDataRowArb: fc.Arbitrary<Record<string, string | number>> = fc.record(
Object.fromEntries(
CSV_HEADERS.map((h) => [
h,
STRING_FIELDS.includes(h)
? safeStringArb
: fc.float({ min: 0, max: 99999, noNaN: true }).map((n) => Math.round(n * 100) / 100),
])
)
)
// Feature: jewelry-mall, Property 6: 规格数据导入导出 Round-Trip
// **Validates: Requirements 8.3**
describe('Property 6: 规格数据导入导出 Round-Trip', () => {
it('导出后再导入应得到等价数据', () => {
fc.assert(
fc.property(
fc.array(specDataRowArb, { minLength: 1, maxLength: 10 }),
(rows) => {
const csv = generateCSV(rows)
const parsed = parseCSV(csv)
expect(parsed.length).toBe(rows.length)
for (let i = 0; i < rows.length; i++) {
for (const h of CSV_HEADERS) {
if (STRING_FIELDS.includes(h)) {
// CSV round-trip trims whitespace, which is expected behavior
expect(parsed[i][h]).toBe(String(rows[i][h]).trim())
} else {
expect(Number(parsed[i][h])).toBeCloseTo(Number(rows[i][h]), 2)
}
}
}
}
),
{ numRuns: 100 }
)
})
})