Files
BIPMaterialManager/tests/unit/services/erp/order-resolver.test.ts
Misaka d0f8ad0fef test(erp): add unit tests for ERP services and test coverage docs
Add unit tests for core ERP service modules including ErpBrowserManager,
cleaner, erp-auth, extractor-core, extractor, and order-resolver. Also
includes test coverage improvement plan and quality review report.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 17:23:00 +08:00

364 lines
13 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { OrderNumberResolver } from '../../../../src/main/services/erp/order-resolver'
import type { IDatabaseService } from '../../../../src/main/services/database'
// Mock logger
vi.mock('../../../../src/main/services/logger', () => ({
createLogger: vi.fn(() => ({
info: vi.fn(),
debug: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}))
}))
// Mock ConfigManager
vi.mock('../../../../src/main/services/config/config-manager', () => ({
ConfigManager: {
getInstance: vi.fn().mockReturnValue({
getConfig: vi.fn().mockReturnValue({
orderResolution: {
tableName: 'test_table',
productionIdField: '总排号',
orderNumberField: '生产订单号'
}
})
})
}
}))
describe('OrderNumberResolver', () => {
const mockDbService = {
type: 'mysql' as const,
connect: vi.fn(),
disconnect: vi.fn(),
isConnected: vi.fn().mockReturnValue(true),
query: vi.fn()
} as unknown as IDatabaseService
let resolver: OrderNumberResolver
beforeEach(() => {
vi.clearAllMocks()
resolver = new OrderNumberResolver(mockDbService)
})
describe('resolve()', () => {
it('resolves order numbers to production IDs', async () => {
vi.mocked(mockDbService.query).mockResolvedValue({
rows: [{ : 'SC70202602120085' }],
columns: ['生产订单号'],
rowCount: 1
})
const results = await resolver.resolve(['22A1'])
expect(results).toHaveLength(1)
expect(mockDbService.query).toHaveBeenCalled()
})
it('batches orders correctly', async () => {
vi.mocked(mockDbService.query).mockResolvedValue({
rows: [
{ : '22A1', : 'SC70202602120085' },
{ : '22A2', : 'SC70202602120086' }
],
columns: ['总排号', '生产订单号'],
rowCount: 2
})
const results = await resolver.resolve(['22A1', '22A2'])
expect(results).toHaveLength(2)
expect(results[0].resolved).toBe(true)
expect(results[1].resolved).toBe(true)
})
it('handles missing orders', async () => {
vi.mocked(mockDbService.query).mockResolvedValue({
rows: [],
columns: [],
rowCount: 0
})
const results = await resolver.resolve(['22A999'])
expect(results).toHaveLength(1)
expect(results[0].resolved).toBe(false)
expect(results[0].error).toBeDefined()
})
it('handles mixed input (productionIds and orderNumbers)', async () => {
vi.mocked(mockDbService.query).mockResolvedValue({
rows: [{ : '22A1', : 'SC70202602120085' }],
columns: ['总排号', '生产订单号'],
rowCount: 1
})
const results = await resolver.resolve(['22A1', 'SC70202602120086'])
expect(results).toHaveLength(2)
expect(results[0].productionId).toBe('22A1')
expect(results[0].orderNumber).toBe('SC70202602120085')
expect(results[0].resolved).toBe(true)
expect(results[1].orderNumber).toBe('SC70202602120086')
expect(results[1].resolved).toBe(true)
})
it('handles unrecognized input format', async () => {
const results = await resolver.resolve(['INVALID_FORMAT'])
expect(results).toHaveLength(1)
expect(results[0].resolved).toBe(false)
expect(results[0].error).toContain('格式不识别')
})
it('deduplicates identical inputs', async () => {
vi.mocked(mockDbService.query).mockResolvedValue({
rows: [{ : '22A1', : 'SC70202602120085' }],
columns: ['总排号', '生产订单号'],
rowCount: 1
})
const results = await resolver.resolve(['22A1', '22A1', '22A1'])
expect(results).toHaveLength(1) // deduplicated
expect(results[0].resolved).toBe(true)
})
})
describe('mapProductionIdToOrderNumber()', () => {
it('uses database service for lookup', async () => {
vi.mocked(mockDbService.query).mockResolvedValue({
rows: [{ : 'SC70202602120085' }],
columns: ['生产订单号'],
rowCount: 1
})
const result = await resolver.mapProductionIdToOrderNumber('22A1')
expect(mockDbService.query).toHaveBeenCalled()
expect(result).toBe('SC70202602120085')
})
it('queries database for each call (no caching)', async () => {
vi.mocked(mockDbService.query).mockResolvedValue({
rows: [{ : 'SC70202602120085' }],
columns: ['生产订单号'],
rowCount: 1
})
// First call
const result1 = await resolver.mapProductionIdToOrderNumber('22A1')
// Second call with same input
const result2 = await resolver.mapProductionIdToOrderNumber('22A1')
expect(result1).toBe(result2)
// Should query twice as there's no explicit caching in this method
expect(mockDbService.query).toHaveBeenCalledTimes(2)
})
})
describe('mapProductionIdsToOrderNumbers()', () => {
it('caching works correctly - batch deduplication', async () => {
vi.mocked(mockDbService.query).mockResolvedValue({
rows: [{ : '22A1', : 'SC70202602120085' }],
columns: ['总排号', '生产订单号'],
rowCount: 1
})
// Should internally deduplicate
await resolver.mapProductionIdsToOrderNumbers(['22A1', '22A1', '22A1'])
// Should be optimized to query unique values only
expect(mockDbService.query).toHaveBeenCalledTimes(1)
})
})
describe('error handling', () => {
it('error handling for database failures', async () => {
vi.mocked(mockDbService.query).mockRejectedValue(new Error('Database connection failed'))
await expect(resolver.mapProductionIdToOrderNumber('22A1')).rejects.toThrow(
'Database connection failed'
)
})
})
describe('getValidOrderNumbers()', () => {
it('returns deduplicated order numbers', async () => {
const mappings = [
{ input: '22A1', resolved: true, orderNumber: 'SC70202602120085' },
{ input: 'SC70202602120086', resolved: true, orderNumber: 'SC70202602120086' },
{ input: '22A1', resolved: true, orderNumber: 'SC70202602120085' } // Duplicate
]
const validOrderNumbers = resolver.getValidOrderNumbers(mappings as any)
expect(validOrderNumbers).toHaveLength(2)
expect(validOrderNumbers).toEqual(['SC70202602120085', 'SC70202602120086'])
})
})
describe('performance', () => {
it('performance with large order sets', async () => {
const largeInput = Array.from({ length: 100 }, (_, i) => `22A${i}`)
vi.mocked(mockDbService.query).mockResolvedValue({
rows: largeInput.map((prodId, i) => ({
总排号: prodId,
: `SC7020260212${String(i).padStart(5, '0')}`
})),
columns: ['总排号', '生产订单号'],
rowCount: largeInput.length
})
const startTime = Date.now()
const results = await resolver.resolve(largeInput)
const elapsed = Date.now() - startTime
expect(results).toHaveLength(largeInput.length)
expect(elapsed).toBeLessThan(5000) // Should complete within 5 seconds
})
})
describe('isProductionId()', () => {
it('should recognize valid production IDs', () => {
expect(resolver.isProductionId('22A1')).toBe(true)
expect(resolver.isProductionId('26B10617')).toBe(true)
expect(resolver.isProductionId('99Z999999')).toBe(true)
expect(resolver.isProductionId('00A0')).toBe(true)
})
it('should reject invalid formats', () => {
expect(resolver.isProductionId('SC70202602120085')).toBe(false) // order number, not production ID
expect(resolver.isProductionId('abc')).toBe(false)
expect(resolver.isProductionId('1A')).toBe(false)
expect(resolver.isProductionId('22AA1')).toBe(false)
expect(resolver.isProductionId('')).toBe(false)
expect(resolver.isProductionId('2A1')).toBe(false) // only 1 digit before letter
})
})
describe('isOrderNumber()', () => {
it('should recognize valid order numbers', () => {
expect(resolver.isOrderNumber('SC70202602120085')).toBe(true)
})
it('should reject invalid formats', () => {
expect(resolver.isOrderNumber('22A1')).toBe(false) // production ID
expect(resolver.isOrderNumber('SC123')).toBe(false) // too short
expect(resolver.isOrderNumber('SC702026021200')).toBe(false) // only 13 digits
expect(resolver.isOrderNumber('XX70202602120085')).toBe(false) // wrong prefix
expect(resolver.isOrderNumber('')).toBe(false)
})
})
describe('recognizeType()', () => {
it('should return productionId for production IDs', () => {
expect(resolver.recognizeType('22A1')).toBe('productionId')
})
it('should return orderNumber for order numbers', () => {
expect(resolver.recognizeType('SC70202602120085')).toBe('orderNumber')
})
it('should return unknown for unrecognized formats', () => {
expect(resolver.recognizeType('abc')).toBe('unknown')
expect(resolver.recognizeType('')).toBe('unknown')
})
})
describe('getWarnings()', () => {
it('should return empty array when all mappings resolved', () => {
const mappings = [
{ input: '22A1', resolved: true, orderNumber: 'SC70202602120085' },
{ input: 'SC70202602120086', resolved: true, orderNumber: 'SC70202602120086' }
]
expect(resolver.getWarnings(mappings as any)).toEqual([])
})
it('should return formatted warnings for failed mappings', () => {
const mappings = [
{ input: '22A999', resolved: false, error: '未在数据库中找到对应的订单号' },
{
input: 'abc',
resolved: false,
error: '格式不识别:既不是有效的生产订单号也不是总排号格式'
}
]
const warnings = resolver.getWarnings(mappings as any)
expect(warnings).toHaveLength(2)
expect(warnings[0]).toBe('22A999: 未在数据库中找到对应的订单号')
expect(warnings[1]).toContain('格式不识别')
})
})
describe('getStats()', () => {
it('should compute correct stats for mixed results', () => {
const mappings = [
{ input: 'SC70202602120085', resolved: true, orderNumber: 'SC70202602120085' },
{ input: '22A1', resolved: true, productionId: '22A1', orderNumber: 'SC70202602120085' },
{ input: '22A999', resolved: false, error: 'not found', productionId: '22A999' },
{ input: 'abc', resolved: false, error: 'unknown format' }
]
const stats = resolver.getStats(mappings as any)
expect(stats.totalInputs).toBe(4)
expect(stats.validOrderNumbers).toBe(1) // only direct order number input
expect(stats.validProductionIds).toBe(1) // only resolved production IDs count
expect(stats.resolvedCount).toBe(2)
expect(stats.failedCount).toBe(2)
expect(stats.unknownFormat).toBe(1) // only 'abc'
})
it('should compute all-success stats', () => {
const mappings = [
{ input: 'SC70202602120085', resolved: true, orderNumber: 'SC70202602120085' },
{ input: '22A1', resolved: true, productionId: '22A1', orderNumber: 'SC70202602120086' }
]
const stats = resolver.getStats(mappings as any)
expect(stats.resolvedCount).toBe(2)
expect(stats.failedCount).toBe(0)
expect(stats.unknownFormat).toBe(0)
})
it('should compute all-failure stats with unknown formats', () => {
const mappings = [
{ input: 'abc', resolved: false, error: 'unknown' },
{ input: 'xyz', resolved: false, error: 'unknown' }
]
const stats = resolver.getStats(mappings as any)
expect(stats.resolvedCount).toBe(0)
expect(stats.failedCount).toBe(2)
expect(stats.unknownFormat).toBe(2)
})
})
describe('getDeduplicationReport()', () => {
it('should report duplicates when inputs exceed unique order numbers', () => {
const mappings = [
{ input: '22A1', resolved: true, orderNumber: 'SC70202602120085' },
{ input: '22A2', resolved: true, orderNumber: 'SC70202602120085' },
{ input: '22A3', resolved: true, orderNumber: 'SC70202602120086' }
]
const report = resolver.getDeduplicationReport(mappings as any)
expect(report.inputCount).toBe(3)
expect(report.uniqueOrderNumbersCount).toBe(2)
expect(report.summary).toContain('重复已合并')
expect(report.orderNumberGroups.get('SC70202602120085')).toEqual(['22A1', '22A2'])
expect(report.orderNumberGroups.get('SC70202602120086')).toEqual(['22A3'])
})
it('should report no duplicates when all inputs map to unique order numbers', () => {
const mappings = [
{ input: '22A1', resolved: true, orderNumber: 'SC70202602120085' },
{ input: '22A2', resolved: true, orderNumber: 'SC70202602120086' }
]
const report = resolver.getDeduplicationReport(mappings as any)
expect(report.inputCount).toBe(2)
expect(report.uniqueOrderNumbersCount).toBe(2)
expect(report.summary).not.toContain('重复已合并')
})
})
})