- 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>
314 lines
8.9 KiB
TypeScript
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')
|
|
})
|
|
})
|
|
})
|