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>
This commit is contained in:
Misaka
2026-04-05 17:23:00 +08:00
parent 4a7c220baa
commit d0f8ad0fef
9 changed files with 3743 additions and 0 deletions

View File

@@ -0,0 +1,252 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { ErpBrowserManager } from '../../../../src/main/services/erp/ErpBrowserManager'
import { chromium } from 'playwright'
// Mock playwright
vi.mock('playwright', () => ({
chromium: {
launch: vi.fn(),
connect: vi.fn()
}
}))
// Mock logger
vi.mock('../../../../src/main/services/logger', () => ({
createLogger: vi.fn(() => ({
info: vi.fn(),
debug: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}))
}))
describe('ErpBrowserManager', () => {
let mockBrowser: any
let mockContext: any
let mockPage: any
beforeEach(() => {
vi.clearAllMocks()
// Create fresh mocks for each test to preserve isConnected state
mockBrowser = {
isConnected: vi.fn().mockReturnValue(true),
newContext: vi.fn(),
close: vi.fn().mockResolvedValue(undefined)
}
mockContext = {
close: vi.fn().mockResolvedValue(undefined),
newPage: vi.fn()
}
mockPage = {
goto: vi.fn().mockResolvedValue(undefined),
waitForLoadState: vi.fn().mockResolvedValue(undefined),
close: vi.fn().mockResolvedValue(undefined)
}
vi.mocked(chromium.launch).mockResolvedValue(mockBrowser)
mockBrowser.newContext.mockResolvedValue(mockContext)
mockContext.newPage.mockResolvedValue(mockPage)
})
describe('launch()', () => {
it('should launch browser with config', async () => {
const manager = new ErpBrowserManager({ headless: false })
const browser = await manager.launch()
expect(chromium.launch).toHaveBeenCalledWith(
expect.objectContaining({
headless: false
})
)
expect(browser).toBeDefined()
})
it('should return existing browser if running', async () => {
const manager = new ErpBrowserManager()
// First launch creates session
const firstBrowser = await manager.launch()
// Force save session (this simulates what initialize() would do)
manager['session'] = {
browser: firstBrowser,
context: mockContext,
page: mockPage
}
// Second launch should return existing browser
const secondBrowser = await manager.launch()
expect(firstBrowser).toBe(secondBrowser)
expect(chromium.launch).toHaveBeenCalledTimes(1)
})
it.each([true, false])('should launch with headless=%s', async (headless) => {
const manager = new ErpBrowserManager({ headless })
await manager.launch()
expect(chromium.launch).toHaveBeenCalledWith(expect.objectContaining({ headless }))
})
})
describe('initialize()', () => {
it('should create browser, context, and page in one call', async () => {
const manager = new ErpBrowserManager({ headless: true })
const session = await manager.initialize()
expect(session.browser).toBe(mockBrowser)
expect(session.context).toBe(mockContext)
expect(session.page).toBe(mockPage)
expect(chromium.launch).toHaveBeenCalledTimes(1)
expect(mockBrowser.newContext).toHaveBeenCalledTimes(1)
expect(mockContext.newPage).toHaveBeenCalledTimes(1)
})
it('should return existing session on repeated calls', async () => {
const manager = new ErpBrowserManager()
const first = await manager.initialize()
const second = await manager.initialize()
expect(first).toBe(second)
expect(chromium.launch).toHaveBeenCalledTimes(1)
})
})
describe('getSession()', () => {
it('should return null when no session', () => {
const manager = new ErpBrowserManager()
expect(manager.getSession()).toBeNull()
})
it('should return session after initialize', async () => {
const manager = new ErpBrowserManager()
const initSession = await manager.initialize()
const session = manager.getSession()
expect(session).toBe(initSession)
})
})
describe('isRunning()', () => {
it('should return false when no session', () => {
const manager = new ErpBrowserManager()
expect(manager.isRunning()).toBe(false)
})
it('should return true when browser is connected', async () => {
const manager = new ErpBrowserManager()
await manager.initialize()
expect(manager.isRunning()).toBe(true)
})
it('should return false when browser is disconnected', async () => {
const manager = new ErpBrowserManager()
await manager.initialize()
mockBrowser.isConnected.mockReturnValue(false)
expect(manager.isRunning()).toBe(false)
})
})
describe('navigate()', () => {
it('should call page.goto and waitForLoadState', async () => {
const manager = new ErpBrowserManager()
await manager.initialize()
await manager.navigate('https://example.com')
expect(mockPage.goto).toHaveBeenCalledWith(
'https://example.com',
expect.objectContaining({ timeout: 30000 })
)
expect(mockPage.waitForLoadState).toHaveBeenCalledWith(
'domcontentloaded',
expect.objectContaining({ timeout: 10000 })
)
})
it('should throw when no session', async () => {
const manager = new ErpBrowserManager()
await expect(manager.navigate('https://example.com')).rejects.toThrow('No page available')
})
})
describe('createContext()', () => {
it('should create browser context', async () => {
const manager = new ErpBrowserManager()
const context = await manager.createContext()
expect(context).toBeDefined()
expect(mockBrowser.newContext).toHaveBeenCalled()
})
it('should use provided browser', async () => {
const manager = new ErpBrowserManager()
const customBrowser = {
...mockBrowser,
newContext: vi.fn().mockResolvedValue(mockContext)
}
await manager.createContext(customBrowser as any)
expect(customBrowser.newContext).toHaveBeenCalled()
})
it('should launch browser if not provided', async () => {
const manager = new ErpBrowserManager()
await manager.createContext()
expect(chromium.launch).toHaveBeenCalled()
})
})
describe('createPage()', () => {
it('should create page in context', async () => {
const manager = new ErpBrowserManager()
await manager.launch()
const page = await manager.createPage()
expect(page).toBeDefined()
expect(mockContext.newPage).toHaveBeenCalled()
})
})
describe('close()', () => {
it('should close all browser resources', async () => {
const manager = new ErpBrowserManager()
await manager.launch()
const context = await manager.createContext()
const page = await manager.createPage()
// Manually set session since our mocks don't persist internal state
manager['session'] = {
browser: mockBrowser,
context,
page
}
await manager.close()
expect(mockContext.close).toHaveBeenCalled()
expect(mockBrowser.close).toHaveBeenCalled()
})
it('should be no-op if no session', async () => {
const manager = new ErpBrowserManager()
await manager.close()
expect(mockContext.close).not.toHaveBeenCalled()
expect(mockBrowser.close).not.toHaveBeenCalled()
})
it('should close browser even if context.close fails', async () => {
const manager = new ErpBrowserManager()
await manager.initialize()
mockContext.close.mockRejectedValue(new Error('Context close error'))
await manager.close()
expect(mockBrowser.close).toHaveBeenCalled()
expect(manager.getSession()).toBeNull()
})
})
})