Files
BIPMaterialManager/tests/unit/report-analyzer.test.ts
Misaka_Company 255fd7e00b feat: add report analysis and visualization feature
- Add recharts library for data visualization
- Implement ReportAnalyzer service to parse report data
- Add report analysis IPC handler with Admin permission check
- Add ReportAnalysisDialog component with charts
- Add unit and E2E tests for report analyzer
- Update Playwright config to exclude vitest-specific test files
- Expand vitest config to include source code tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 08:57:37 +08:00

314 lines
8.9 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { ReportAnalyzer } from '../../src/main/services/report/report-analyzer'
describe('ReportAnalyzer', () => {
const analyzer = new ReportAnalyzer()
describe('parseMarkdownReport', () => {
it('should parse standard report with all fields', () => {
const content = `## 执行摘要
| **操作用户** | \`admin\` |
| **处理订单数** | \`150\` |
| **删除物料数** | \`45\` |
| **跳过物料数** | \`30\` |
| **错误数量** | \`2\` |
| **重试订单数** | \`10\` |
| **成功重试数** | \`8\` |
| **执行耗时** | \`2 分 30 秒\` |
| **执行时间** | \`2026-03-25 08:30:45\` |
`
const result = analyzer.parseMarkdownReport(content)
expect(result).not.toBeNull()
expect(result!.date).toBe('2026-03-25')
expect(result!.username).toBe('admin')
expect(result!.ordersProcessed).toBe(150)
expect(result!.materialsDeleted).toBe(45)
expect(result!.materialsSkipped).toBe(30)
expect(result!.errorCount).toBe(2)
expect(result!.retriedOrders).toBe(10)
expect(result!.successfulRetries).toBe(8)
expect(result!.durationSeconds).toBe(150)
})
it('should use defaults for missing fields', () => {
const content = `## 执行摘要
| **操作用户** | \`user1\` |
| **处理订单数** | \`50\` |
`
const result = analyzer.parseMarkdownReport(content)
expect(result).not.toBeNull()
expect(result!.username).toBe('user1')
expect(result!.ordersProcessed).toBe(50)
expect(result!.materialsDeleted).toBe(0)
expect(result!.materialsSkipped).toBe(0)
expect(result!.errorCount).toBe(0)
expect(result!.retriedOrders).toBe(0)
expect(result!.successfulRetries).toBe(0)
expect(result!.durationSeconds).toBe(0)
})
it('should return null for malformed format', () => {
const content = `This is not a valid report format
Just some random text without proper structure
No execution summary section here`
const result = analyzer.parseMarkdownReport(content)
expect(result).toBeNull()
})
it('should return null for empty content', () => {
expect(analyzer.parseMarkdownReport('')).toBeNull()
expect(analyzer.parseMarkdownReport(' ')).toBeNull()
})
it('should handle special characters in fields', () => {
const content = `## 执行摘要
| **操作用户** | \`admin@test.com\` |
| **处理订单数** | \`100\` |
| **删除物料数** | \`25\` |
| **跳过物料数** | \`10\` |
| **错误数量** | \`0\` |
| **重试订单数** | \`5\` |
| **成功重试数** | \`5\` |
| **执行耗时** | \`1 分\` |
| **执行时间** | \`2026-03-25 10:00:00\` |
`
const result = analyzer.parseMarkdownReport(content)
expect(result).not.toBeNull()
expect(result!.username).toBe('admin@test.com')
expect(result!.durationSeconds).toBe(60)
})
it('should extract date from content when execution time missing', () => {
const content = `## 执行摘要
| **操作用户** | \`test\` |
| **处理订单数** | \`20\` |
| **删除物料数** | \`5\` |
| **跳过物料数** | \`2\` |
| **错误数量** | \`0\` |
| **重试订单数** | \`0\` |
| **成功重试数** | \`0\` |
| **执行耗时** | \`30 秒\` |
`
const result = analyzer.parseMarkdownReport(content)
expect(result).not.toBeNull()
expect(result!.date).toMatch(/^\d{4}-\d{2}-\d{2}$/)
})
it('should handle various duration formats', () => {
// Test "2 分 30 秒"
const content1 = `## 执行摘要
| **操作用户** | \`test\` |
| **处理订单数** | \`10\` |
| **删除物料数** | \`5\` |
| **跳过物料数** | \`2\` |
| **错误数量** | \`0\` |
| **重试订单数** | \`0\` |
| **成功重试数** | \`0\` |
| **执行耗时** | \`2 分 30 秒\` |
| **执行时间** | \`2026-03-25 10:00:00\` |
`
const result1 = analyzer.parseMarkdownReport(content1)
expect(result1!.durationSeconds).toBe(150)
// Test "1 分"
const content2 = `## 执行摘要
| **操作用户** | \`test\` |
| **处理订单数** | \`10\` |
| **删除物料数** | \`5\` |
| **跳过物料数** | \`2\` |
| **错误数量** | \`0\` |
| **重试订单数** | \`0\` |
| **成功重试数** | \`0\` |
| **执行耗时** | \`1 分\` |
| **执行时间** | \`2026-03-25 10:00:00\` |
`
const result2 = analyzer.parseMarkdownReport(content2)
expect(result2!.durationSeconds).toBe(60)
// Test "30 秒" - use different time to avoid confusion
const content3 = `## 执行摘要
| **操作用户** | \`test\` |
| **处理订单数** | \`10\` |
| **删除物料数** | \`5\` |
| **跳过物料数** | \`2\` |
| **错误数量** | \`0\` |
| **重试订单数** | \`0\` |
| **成功重试数** | \`0\` |
| **执行耗时** | \`30 秒\` |
| **执行时间** | \`2026-03-25 14:20:15\` |
`
const result3 = analyzer.parseMarkdownReport(content3)
console.log(
'Duration test - extracted duration:',
result3?.durationSeconds,
'content:',
content3
)
expect(result3!.durationSeconds).toBe(30)
})
})
describe('analyzeReports', () => {
it('should batch process multiple reports', () => {
const reports = [
{
content: `## 执行摘要
| **操作用户** | \`user1\` |
| **处理订单数** | \`100\` |
| **删除物料数** | \`20\` |
| **跳过物料数** | \`10\` |
| **错误数量** | \`1\` |
| **重试订单数** | \`5\` |
| **成功重试数** | \`4\` |
| **执行耗时** | \`1 分\` |
| **执行时间** | \`2026-03-25 08:00:00\` |
`,
filename: 'report-2026-03-25.md'
},
{
content: `## 执行摘要
| **操作用户** | \`user2\` |
| **处理订单数** | \`200\` |
| **删除物料数** | \`50\` |
| **跳过物料数** | \`20\` |
| **错误数量** | \`2\` |
| **重试订单数** | \`10\` |
| **成功重试数** | \`8\` |
| **执行耗时** | \`2 分\` |
| **执行时间** | \`2026-03-26 09:00:00\` |
`,
filename: 'report-2026-03-26.md'
}
]
const results = analyzer.analyzeReports(reports)
expect(results.length).toBe(2)
expect(results[0].username).toBe('user1')
expect(results[0].date).toBe('2026-03-25')
expect(results[1].username).toBe('user2')
expect(results[1].date).toBe('2026-03-26')
})
it('should skip invalid reports in batch', () => {
const reports = [
{
content: `## 执行摘要
| **操作用户** | \`valid\` |
| **处理订单数** | \`50\` |
| **删除物料数** | \`10\` |
| **跳过物料数** | \`5\` |
| **错误数量** | \`0\` |
| **重试订单数** | \`0\` |
| **成功重试数** | \`0\` |
| **执行耗时** | \`30 秒\` |
| **执行时间** | \`2026-03-25 10:00:00\` |
`,
filename: 'valid-report.md'
},
{
content: 'Invalid report content',
filename: 'invalid-report.md'
}
]
const results = analyzer.analyzeReports(reports)
expect(results.length).toBe(1)
expect(results[0].username).toBe('valid')
})
it('should use filename date to override content date', () => {
const reports = [
{
content: `## 执行摘要
| **操作用户** | \`test\` |
| **处理订单数** | \`30\` |
| **删除物料数** | \`5\` |
| **跳过物料数** | \`2\` |
| **错误数量** | \`0\` |
| **重试订单数** | \`0\` |
| **成功重试数** | \`0\` |
| **执行耗时** | \`20 秒\` |
| **执行时间** | \`2026-03-20 10:00:00\` |
`,
filename: 'report-2026-03-25-08-30-45.md'
}
]
const results = analyzer.analyzeReports(reports)
expect(results.length).toBe(1)
expect(results[0].date).toBe('2026-03-25')
})
})
describe('date extraction', () => {
it('should extract date from filename with ISO format', () => {
const content = `## 执行摘要
| **操作用户** | \`test\` |
| **处理订单数** | \`10\` |
| **删除物料数** | \`5\` |
| **跳过物料数** | \`2\` |
| **错误数量** | \`0\` |
| **重试订单数** | \`0\` |
| **成功重试数** | \`0\` |
| **执行耗时** | \`10 秒\` |
| **执行时间** | \`2026-03-20 10:00:00\` |
`
const report = {
content,
filename: 'cleaner-report-2026-03-25-08-30-45.md'
}
const results = analyzer.analyzeReports([report])
expect(results[0].date).toBe('2026-03-25')
})
it('should extract date from filename with YYYYMMDD format', () => {
const content = `## 执行摘要
| **操作用户** | \`test\` |
| **处理订单数** | \`10\` |
| **删除物料数** | \`5\` |
| **跳过物料数** | \`2\` |
| **错误数量** | \`0\` |
| **重试订单数** | \`0\` |
| **成功重试数** | \`0\` |
| **执行耗时** | \`10 秒\` |
| **执行时间** | \`2026-03-20 10:00:00\` |
`
const report = {
content,
filename: 'report-20260325-100000.md'
}
const results = analyzer.analyzeReports([report])
expect(results[0].date).toBe('2026-03-25')
})
})
})