refactor(test): migrate e2e to Playwright and remove duplicate unit tests

Switch extractor-workflow e2e test from vitest to Playwright test runner
for consistency with playwright.config.ts. Remove redundant unit tests
(cleaner, erp-auth, extractor) that have been superseded by more thorough
replacements under tests/unit/services/erp/. Enable test isolation
unconditionally to prevent cross-file state pollution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-04-06 11:55:58 +08:00
parent fe6cdbf076
commit d0c745e243
5 changed files with 18 additions and 268 deletions

View File

@@ -1,145 +0,0 @@
import { describe, it, expect } from 'vitest'
import {
CleanerService,
createBatches,
getMissingOrders,
runWithConcurrency
} from '../../src/main/services/erp/cleaner'
describe('Cleaner Service (Unit)', () => {
describe('shouldDeleteMaterial', () => {
// CleanerService constructor requires ErpAuthService, but shouldDeleteMaterial doesn't use it
const cleaner = new CleanerService({} as any)
it('should skip materials with row number 2000-7999', () => {
const testCases = [
{ rowNumber: 2000, pendingQty: '', materialCode: 'TEST001', expected: false },
{ rowNumber: 5000, pendingQty: '', materialCode: 'TEST001', expected: false },
{ rowNumber: 7999, pendingQty: '', materialCode: 'TEST001', expected: false },
{ rowNumber: 1999, pendingQty: '', materialCode: 'TEST001', expected: true },
{ rowNumber: 8000, pendingQty: '', materialCode: 'TEST001', expected: true }
]
for (const tc of testCases) {
const shouldDelete = cleaner.shouldDeleteMaterial({
rowNumber: tc.rowNumber,
pendingQty: tc.pendingQty,
materialCode: tc.materialCode,
deleteSet: new Set(['TEST001'])
})
expect(shouldDelete).toBe(tc.expected)
}
})
it('should skip materials with non-empty pending quantity', () => {
const result = cleaner.shouldDeleteMaterial({
rowNumber: 100,
pendingQty: '5',
materialCode: 'TEST001',
deleteSet: new Set(['TEST001'])
})
expect(result).toBe(false)
})
it('should skip materials not in delete list', () => {
const result = cleaner.shouldDeleteMaterial({
rowNumber: 100,
pendingQty: '',
materialCode: 'NOT_IN_LIST',
deleteSet: new Set(['TEST001'])
})
expect(result).toBe(false)
})
it('should delete materials with empty pending qty and valid row number', () => {
const testCases = [
{ rowNumber: 1, pendingQty: '', materialCode: 'TEST001', expected: true },
{ rowNumber: 100, pendingQty: '', materialCode: 'TEST001', expected: true },
{ rowNumber: 1999, pendingQty: '', materialCode: 'TEST001', expected: true },
{ rowNumber: 8000, pendingQty: '', materialCode: 'TEST001', expected: true },
{ rowNumber: 10000, pendingQty: '', materialCode: 'TEST001', expected: true }
]
for (const tc of testCases) {
const shouldDelete = cleaner.shouldDeleteMaterial({
rowNumber: tc.rowNumber,
pendingQty: tc.pendingQty,
materialCode: tc.materialCode,
deleteSet: new Set(['TEST001'])
})
expect(shouldDelete).toBe(tc.expected)
}
})
it('should handle multiple conditions correctly', () => {
// Material in list, valid row, no pending qty = should delete
expect(
cleaner.shouldDeleteMaterial({
rowNumber: 100,
pendingQty: '',
materialCode: 'TEST001',
deleteSet: new Set(['TEST001'])
})
).toBe(true)
// Material in list, protected row, no pending qty = should NOT delete
expect(
cleaner.shouldDeleteMaterial({
rowNumber: 7500,
pendingQty: '',
materialCode: 'TEST001',
deleteSet: new Set(['TEST001'])
})
).toBe(false)
// Material in list, valid row, has pending qty = should NOT delete
expect(
cleaner.shouldDeleteMaterial({
rowNumber: 100,
pendingQty: '10',
materialCode: 'TEST001',
deleteSet: new Set(['TEST001'])
})
).toBe(false)
// Material NOT in list = should NOT delete
expect(
cleaner.shouldDeleteMaterial({
rowNumber: 100,
pendingQty: '',
materialCode: 'OTHER',
deleteSet: new Set(['TEST001'])
})
).toBe(false)
})
})
describe('batch and concurrency helpers', () => {
it('should split orders into batches', () => {
const batches = createBatches(['A', 'B', 'C', 'D', 'E'], 2)
expect(batches).toEqual([['A', 'B'], ['C', 'D'], ['E']])
})
it('should identify missing orders', () => {
const missing = getMissingOrders(['SC1', 'SC2', 'SC3'], new Set(['SC1', 'SC3']))
expect(missing).toEqual(['SC2'])
})
it('should respect concurrency limit', async () => {
const items = [1, 2, 3, 4, 5, 6]
let running = 0
let peak = 0
await runWithConcurrency(items, 2, async () => {
running += 1
peak = Math.max(peak, running)
await new Promise((resolve) => setTimeout(resolve, 10))
running -= 1
return true
})
expect(peak).toBeLessThanOrEqual(2)
})
})
})

View File

@@ -1,28 +0,0 @@
import { describe, it, expect } from 'vitest'
import { ErpAuthService } from '../../src/main/services/erp/erp-auth'
import type { ErpConfig } from '../../src/main/types/erp.types'
const testConfig: ErpConfig = {
url: 'https://test.example.com',
username: 'testuser',
password: 'testpass'
}
describe('ERP Authentication Service (Unit)', () => {
describe('Initial State', () => {
it('should report inactive status before login', () => {
const service = new ErpAuthService(testConfig)
expect(service.isActive()).toBe(false)
})
it('should throw error when getting session before login', () => {
const service = new ErpAuthService(testConfig)
expect(() => service.getSession()).toThrow('Not logged in. Call login() first.')
})
it('should handle close when no session exists', async () => {
const service = new ErpAuthService(testConfig)
await expect(service.close()).resolves.toBeUndefined()
})
})
})

View File

@@ -1,70 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { ExtractorService } from '../../src/main/services/erp/extractor'
import { ErpAuthService } from '../../src/main/services/erp/erp-auth'
import type { ErpConfig } from '../../src/main/types/erp.types'
describe('Extractor Service (Unit)', () => {
let authService: ErpAuthService
let extractor: ExtractorService
const mockConfig: ErpConfig = {
url: 'https://test.erp.com',
username: 'test_user',
password: 'test_pass'
}
beforeEach(() => {
authService = new ErpAuthService(mockConfig)
extractor = new ExtractorService(authService, './test-downloads')
})
describe('Service Initialization', () => {
it('should create service instance as ExtractorService', () => {
expect(extractor).toBeInstanceOf(ExtractorService)
})
it('should create service with default download directory', () => {
const defaultExtractor = new ExtractorService(authService)
expect(defaultExtractor).toBeInstanceOf(ExtractorService)
})
it('should create service with custom download directory', () => {
const customExtractor = new ExtractorService(authService, './custom-downloads')
expect(customExtractor).toBeInstanceOf(ExtractorService)
})
})
describe('Error Handling', () => {
it('should handle extraction with no auth session', async () => {
const result = await extractor.extract({
orderNumbers: ['ORDER1']
})
expect(result.errors.length).toBeGreaterThan(0)
expect(result.downloadedFiles).toHaveLength(0)
})
it('should include error message when session is missing', async () => {
const result = await extractor.extract({
orderNumbers: ['ORD-001', 'ORD-002']
})
expect(result.errors).toEqual(
expect.arrayContaining([expect.stringContaining('Not logged in')])
)
})
it('should return empty result structure even on failure', async () => {
const result = await extractor.extract({
orderNumbers: ['ORDER1']
})
expect(result).toHaveProperty('downloadedFiles')
expect(result).toHaveProperty('mergedFile')
expect(result).toHaveProperty('recordCount')
expect(result).toHaveProperty('errors')
expect(result).toHaveProperty('orderRecordCounts')
expect(result.mergedFile).toBeNull()
expect(result.recordCount).toBe(0)
})
})
})