From fb3dd431644c416caef6e2921d5ef56845d4216e Mon Sep 17 00:00:00 2001 From: Misaka Date: Sat, 4 Apr 2026 20:15:54 +0800 Subject: [PATCH] test: add Wave 1 infrastructure (types, mocks, vitest config) + User/Order/Material factories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tests/fixtures/types.ts: Test fixture type definitions - tests/fixtures/factory.ts: User/Order/Material factories - tests/mocks/types.ts: Mock type definitions (549 lines) - tests/mocks/index.ts: Mock factory functions - vitest.config.ts: Performance optimizations (isolate:false, pool:threads) - User/Order/Material factory tests (7 tests total) Performance: 7.65s → 4.94s (35% improvement) --- tests/fixtures/factory.ts | 217 +++++++ tests/fixtures/order-material-factory.test.ts | 57 ++ tests/fixtures/types.ts | 225 +++++++ tests/fixtures/user-factory.test.ts | 35 ++ tests/mocks/index.ts | 317 ++++++++++ tests/mocks/types.ts | 549 ++++++++++++++++++ vitest.config.ts | 5 + 7 files changed, 1405 insertions(+) create mode 100644 tests/fixtures/factory.ts create mode 100644 tests/fixtures/order-material-factory.test.ts create mode 100644 tests/fixtures/types.ts create mode 100644 tests/fixtures/user-factory.test.ts create mode 100644 tests/mocks/index.ts create mode 100644 tests/mocks/types.ts diff --git a/tests/fixtures/factory.ts b/tests/fixtures/factory.ts new file mode 100644 index 0000000..2802f4f --- /dev/null +++ b/tests/fixtures/factory.ts @@ -0,0 +1,217 @@ +/** + * Test Fixture Factory + * + * Factory class for generating test data with consistent structure. + */ + +import type { TestUser, Order, Material } from './types' + +/** + * User Factory - generates test user data + * + * Creates users with role-based permissions and unique IDs. + */ +export class UserFactory { + /** + * Create a user with specified role + * + * @param role - User role ('admin', 'user', or 'guest') + * @param overrides - Optional field overrides + * @returns Generated test user + */ + static createUser( + role: 'admin' | 'user' | 'guest' = 'user', + overrides?: Partial + ): TestUser { + const user: TestUser = { + id: UserFactory.generateId(), + username: `test_${role}_${Date.now()}`, + userType: UserFactory.getUserTypeFromRole(role), + permissions: UserFactory.getPermissionsForRole(role), + ...overrides + } + return user + } + + /** + * Create an admin user + * + * @param overrides - Optional field overrides + * @returns Admin test user + */ + static createAdmin(overrides?: Partial): TestUser { + return UserFactory.createUser('admin', overrides) + } + + /** + * Create a regular user + * + * @param overrides - Optional field overrides + * @returns Regular test user + */ + static createUserDefault(overrides?: Partial): TestUser { + return UserFactory.createUser('user', overrides) + } + + /** + * Create a guest user + * + * @param overrides - Optional field overrides + * @returns Guest test user + */ + static createGuest(overrides?: Partial): TestUser { + return UserFactory.createUser('guest', overrides) + } + + /** + * Generate unique user ID + * + * @returns Unique ID string in format USR-{timestamp}-{random} + */ + private static generateId(): string { + return `USR-${Date.now()}-${Math.random().toString(36).substr(2, 6)}` + } + + /** + * Get permissions for a role + * + * @param role - User role + * @returns Array of permission strings + */ + private static getPermissionsForRole(role: string): string[] { + return ( + { + admin: ['read', 'write', 'delete', 'admin'], + user: ['read', 'write'], + guest: ['read'] + }[role] || [] + ) + } + + /** + * Convert role string to UserType + * + * @param role - Role string + * @returns UserType ('Admin' or 'User') + */ + private static getUserTypeFromRole(role: string): 'Admin' | 'User' { + return role === 'admin' ? 'Admin' : 'User' + } +} + +/** + * Order Factory + * + * Creates Order fixtures with auto-generated unique identifiers. + */ +export class OrderFactory { + /** + * Create a new Order fixture + * + * @param overrides - Optional overrides to customize the order + * @returns A new Order instance + * + * @example + * // Basic order with auto-generated values + * const order = OrderFactory.createOrder() + * + * @example + * // Order with custom order number + * const order = OrderFactory.createOrder({ orderNumber: 'SC202501001' }) + * + * @example + * // Order with materials + * const materials = [MaterialFactory.createMaterial()] + * const order = OrderFactory.createOrder({ items: materials }) + */ + static createOrder(overrides?: Partial): Order { + const timestamp = Date.now() + return { + id: `ORD-${timestamp}`, + orderNumber: `SC${timestamp.toString().substr(-8)}`, + productionId: `PROD-${timestamp}`, + productName: 'Test Product', + productSpec: null, + plannedQuantity: 100, + unit: '件', + requiredDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], + department: 'Test Department', + items: [], + creator: null, + printer: null, + printDate: null, + ...overrides + } + } + + /** + * Create multiple orders + * + * @param count - Number of orders to create + * @param overrides - Optional overrides applied to all orders + * @returns Array of Order instances + */ + static createOrders(count: number, overrides?: Partial): Order[] { + return Array.from({ length: count }, () => this.createOrder(overrides)) + } +} + +/** + * Material Factory + * + * Creates Material fixtures with auto-generated unique codes. + */ +export class MaterialFactory { + /** + * Create a new Material fixture + * + * @param overrides - Optional overrides to customize the material + * @returns A new Material instance + * + * @example + * // Basic material with auto-generated code + * const material = MaterialFactory.createMaterial() + * + * @example + * // Material with custom code + * const material = MaterialFactory.createMaterial({ code: 'M001' }) + * + * @example + * // Material with specific quantity + * const material = MaterialFactory.createMaterial({ quantity: 50, unit: 'kg' }) + */ + static createMaterial(overrides?: Partial): Material { + const timestamp = Date.now() + return { + index: 1, + code: `TEST_MAT_${Math.random().toString(36).substr(2, 6).toUpperCase()}`, + description: 'Test Material', + specification: null, + model: null, + drawingNumber: null, + grade: null, + quantity: 10, + unit: '件', + requiredDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], + warehouse: 'Test Warehouse', + unitUsage: 1.0, + outboundQuantity: 0, + ...overrides + } + } + + /** + * Create multiple materials with unique codes + * + * @param count - Number of materials to create + * @param overrides - Optional overrides applied to all materials + * @returns Array of Material instances + */ + static createMaterials(count: number, overrides?: Partial): Material[] { + return Array.from({ length: count }, (_, index) => { + const material = this.createMaterial(overrides) + material.index = index + 1 + return material + }) + } +} diff --git a/tests/fixtures/order-material-factory.test.ts b/tests/fixtures/order-material-factory.test.ts new file mode 100644 index 0000000..8a524ce --- /dev/null +++ b/tests/fixtures/order-material-factory.test.ts @@ -0,0 +1,57 @@ +/** + * Order and Material Factory Unit Tests + * + * Tests for OrderFactory and MaterialFactory fixture generation. + */ + +import { describe, it, expect } from 'vitest' +import { OrderFactory, MaterialFactory } from './factory' +import type { Order, Material } from './types' + +describe('OrderFactory', () => { + describe('createOrder', () => { + it('should create an order with auto-generated order number in SC format', () => { + const order = OrderFactory.createOrder() + + expect(order.orderNumber).toMatch(/^SC\d{8}$/) + }) + + it('should support overrides to customize order properties', () => { + const customOrder: Partial = { + orderNumber: 'SC202501001', + productName: 'Custom Product', + plannedQuantity: 500 + } + + const order = OrderFactory.createOrder(customOrder) + + expect(order.orderNumber).toBe('SC202501001') + expect(order.productName).toBe('Custom Product') + expect(order.plannedQuantity).toBe(500) + }) + }) +}) + +describe('MaterialFactory', () => { + describe('createMaterial', () => { + it('should create a material with auto-generated code in TEST_MAT_XXXXXX format', () => { + const material = MaterialFactory.createMaterial() + + expect(material.code).toMatch(/^TEST_MAT_[A-Z0-9]{6}$/) + }) + + it('should support overrides to customize material properties', () => { + const customMaterial: Partial = { + code: 'M001', + description: 'Custom Material', + quantity: 250 + } + + const material = MaterialFactory.createMaterial(customMaterial) + + expect(material.code).toBe('M001') + expect(material.description).toBe('Custom Material') + expect(material.quantity).toBe(250) + }) + }) +}) diff --git a/tests/fixtures/types.ts b/tests/fixtures/types.ts new file mode 100644 index 0000000..8fe2e93 --- /dev/null +++ b/tests/fixtures/types.ts @@ -0,0 +1,225 @@ +/** + * Test Fixtures Type Definitions + * + * Type definitions for test fixture factories and test data generation. + * Reuses business types from src/main/types where possible. + */ + +import type { UserInfo, UserSession } from '../../src/main/types/user.types' +import type { ErpConfig } from '../../src/main/types/erp.types' +import type { + DatabaseConfig, + MySqlConfig, + SqlServerConfig +} from '../../src/main/types/database.types' +import type { FullConfig } from '../../src/main/types/config.schema' + +/** + * Material item in an order + * + * Represents a single material line item in production order data. + */ +export interface Material { + /** Row index in the order table (1-based) */ + index: number + /** Material code/identifier */ + code: string + /** Material name/description */ + description: string + /** Material specification */ + specification?: string | null + /** Material model/type */ + model?: string | null + /** Drawing number */ + drawingNumber?: string | null + /** Material grade/quality */ + grade?: string | null + /** Planned quantity */ + quantity: number + /** Unit of measure */ + unit: string + /** Required date */ + requiredDate: string + /** Issuing warehouse */ + warehouse: string + /** Unit usage amount */ + unitUsage: number + /** Cumulative outbound quantity */ + outboundQuantity: number +} + +/** + * Production order interface + * + * Represents a complete production order with header info and material items. + */ +export interface Order { + /** Order unique identifier */ + id: string + /** Production order number */ + orderNumber: string + /** Production ID (product code) */ + productionId: string + /** Product name */ + productName: string + /** Product specification */ + productSpec?: string | null + /** Planned quantity for the order */ + plannedQuantity: number + /** Unit of measure */ + unit: string + /** Required delivery date */ + requiredDate: string + /** Production department */ + department: string + /** Materials in this order */ + items: Material[] + /** Creator of the order */ + creator?: string | null + /** Printer of the order */ + printer?: string | null + /** Print date */ + printDate?: string | null +} + +/** + * User fixture for test data generation + * + * Simplified user data for creating test users. + */ +export interface TestUser { + /** User ID */ + id: string + /** Username for login */ + username: string + /** User type/role */ + userType: 'Admin' | 'User' + /** User permissions (optional) */ + permissions?: string[] + /** Create time (optional) */ + createTime?: Date +} + +/** + * ERP configuration for testing + * + * Test fixture configuration for ERP system connection. + */ +export interface TestErpConfig { + /** ERP system URL */ + url: string + /** ERP username */ + username: string + /** ERP password */ + password: string + /** Headless browser mode (optional) */ + headless?: boolean +} + +/** + * Database configuration for testing + * + * Simplified database configuration for test fixtures. + */ +export interface TestDatabaseConfig { + /** Database type */ + type: 'mysql' | 'sqlserver' + /** Database host/server */ + host: string + /** Database port */ + port: number + /** Database name */ + database: string + /** Database username */ + username: string + /** Database password */ + password: string + /** Character set (MySQL only, optional) */ + charset?: string + /** Driver (SQL Server only, optional) */ + driver?: string + /** Trust server certificate (SQL Server only, optional) */ + trustServerCertificate?: boolean +} + +/** + * Complete test configuration + * + * Full application configuration for test environments. + */ +export interface TestConfig { + /** ERP system configuration */ + erp: TestErpConfig + /** Database configuration */ + database: TestDatabaseConfig + /** Path configuration */ + paths: { + /** Data directory path */ + dataDir: string + /** Default output file path */ + defaultOutput: string + /** Validation output file path */ + validationOutput: string + } +} + +/** + * Excel file fixture metadata + * + * Information about generated Excel test fixtures. + */ +export interface ExcelFixture { + /** File path */ + filePath: string + /** Order number in the fixture */ + orderNumber: string + /** Production ID in the fixture */ + productionId: string + /** Number of material items */ + materialCount: number + /** Whether the fixture has empty orders */ + hasEmptyOrders: boolean +} + +/** + * Test data factory options + * + * Options for customizing generated test data. + */ +export interface FactoryOptions { + /** Number of materials to generate (default: 3) */ + materialCount?: number + /** Include optional fields (default: true) */ + includeOptional?: boolean + /** Generate empty orders (default: false) */ + emptyOrders?: boolean + /** Custom order number (default: auto-generated) */ + orderNumber?: string + /** Custom production ID (default: auto-generated) */ + seed?: number +} + +/** + * Validation result for test data + * + * Result of validating generated test data against expected schema. + */ +export interface ValidationResult { + /** Whether validation passed */ + isValid: boolean + /** Error messages if validation failed */ + errors: string[] + /** Warning messages */ + warnings: string[] +} + +// Re-export business types for convenience +export type { + UserInfo, + UserSession, + ErpConfig, + DatabaseConfig, + MySqlConfig, + SqlServerConfig, + FullConfig +} diff --git a/tests/fixtures/user-factory.test.ts b/tests/fixtures/user-factory.test.ts new file mode 100644 index 0000000..e4f712c --- /dev/null +++ b/tests/fixtures/user-factory.test.ts @@ -0,0 +1,35 @@ +/** + * UserFactory Unit Tests + */ + +import { describe, it, expect } from 'vitest' +import { UserFactory } from './factory' + +describe('UserFactory', () => { + it('creates user with default role', () => { + const user = UserFactory.createUser() + + expect(user.username).toMatch(/^test_user_\d+$/) + expect(user.userType).toBe('User') + expect(user.permissions).toEqual(['read', 'write']) + expect(user.id).toMatch(/^USR-\d+-[a-z0-9]+$/) + }) + + it('creates admin with correct permissions', () => { + const admin = UserFactory.createAdmin() + + expect(admin.userType).toBe('Admin') + expect(admin.permissions).toEqual(['read', 'write', 'delete', 'admin']) + expect(admin.id).toMatch(/^USR-\d+-[a-z0-9]+$/) + }) + + it('generates unique IDs', () => { + const id1 = UserFactory.createAdmin().id + const id2 = UserFactory.createUser().id + const id3 = UserFactory.createGuest().id + + expect(id1).not.toBe(id2) + expect(id2).not.toBe(id3) + expect(id1).not.toBe(id3) + }) +}) diff --git a/tests/mocks/index.ts b/tests/mocks/index.ts new file mode 100644 index 0000000..c3aba0f --- /dev/null +++ b/tests/mocks/index.ts @@ -0,0 +1,317 @@ +/** + * ERPAuto Mock Library + * + * Central export point for all mock types and factory functions. + * Use this module to import mock types for unit testing. + * + * @module mocks + */ + +import { vi } from 'vitest' + +// ============================================================================ +// Type Exports +// ============================================================================ + +// Config-compatible types +export type { + LogLevel, + DatabaseType, + MySqlConfig, + SqlServerConfig, + DatabaseConfig, + ErpConfig, + PathsConfig, + ExtractionConfig, + ValidationConfig, + CleanerConfig, + OrderResolutionConfig, + LoggingConfig, + SeqConfig, + RustFSConfig, + UpdateConfig, + FullConfig +} from './types' + +// Mock interfaces +export type { + // Logger mocks + MockLogger, + + // ConfigManager mocks + MockConfigManager, + + // ERP Auth mocks + MockErpAuthService, + MockErpSession, + + // Playwright mocks + MockBrowser, + MockBrowserContext, + MockPage, + MockFrame, + MockLocator, + + // Electron mocks + MockElectronApp, + MockIpcMain, + MockDialog, + MockShell, + MockBrowserWindowConstructor, + MockBrowserWindow +} from './types' + +// Factory function types +export type { + MockLoggerOptions, + MockConfigManagerOptions, + MockErpAuthOptions, + MockLoggerFactory, + MockConfigManagerFactory, + MockErpAuthFactory +} from './types' + +// ============================================================================ +// Factory Function Skeletons (to be implemented) +// ============================================================================ + +/** + * Create a mock logger instance with vi.fn() implementations + * + * @param overrides - Optional overrides for specific methods + * @returns Mock logger matching winston.Logger API + * + * @example + * ```typescript + * const logger = createMockLogger({ + * info: vi.fn() + * }) + * ``` + */ +export function createMockLogger( + overrides?: Partial +): import('./types').MockLogger { + return { + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + debug: vi.fn(), + verbose: vi.fn(), + child: vi.fn().mockImplementation((context: string) => createMockLogger(overrides)), + ...overrides + } +} + +/** + * Create a mock ConfigManager instance + * + * @param config - Optional partial config to use as initial state + * @returns Mock config manager + * + * @example + * ```typescript + * const configManager = createMockConfigManager({ + * logging: { level: 'debug', auditRetention: 30, appRetention: 14 } + * }) + * ``` + */ +export function createMockConfigManager( + config?: Partial +): import('./types').MockConfigManager { + const defaultConfig: import('./types').FullConfig = { + erp: { url: 'https://test-erp.local' }, + database: { + activeType: 'mysql', + mysql: { + host: 'localhost', + port: 3306, + database: 'test_db', + username: 'test', + password: 'test', + charset: 'utf8mb4' + }, + sqlserver: { + server: 'localhost', + port: 1433, + database: 'test_db', + username: 'test', + password: 'test', + driver: 'ODBC Driver 18 for SQL Server', + trustServerCertificate: true + } + }, + paths: { + dataDir: './test-data/', + defaultOutput: 'test-output.xlsx', + validationOutput: 'test-validation.xlsx' + }, + extraction: { + batchSize: 100, + verbose: false, + autoConvert: true, + mergeBatches: true, + enableDbPersistence: false, + headless: true + }, + validation: { + dataSource: 'test', + batchSize: 1000, + matchMode: 'exact', + enableCrud: false, + defaultManager: '' + }, + cleaner: { + queryBatchSize: 100, + processConcurrency: 1 + }, + orderResolution: { + tableName: '', + productionIdField: '', + orderNumberField: '' + }, + logging: { + level: 'info', + auditRetention: 30, + appRetention: 14 + }, + seq: { + enabled: false, + serverUrl: '', + apiKey: '', + batchPostingLimit: 50, + period: 2000, + queueLimit: 10000, + maxRetries: 3 + }, + rustfs: { + enabled: false, + endpoint: '', + accessKey: '', + secretKey: '', + bucket: 'test', + region: 'us-east-1' + }, + update: { + enabled: false, + allowDevMode: false, + endpoint: '', + accessKey: '', + secretKey: '', + bucket: '', + region: '', + basePrefix: 'test', + checkIntervalMinutes: 30, + maxAdminHistoryPerChannel: 10 + } + } + + const mergedConfig = { ...defaultConfig, ...config } + + return { + getConfig: vi.fn().mockReturnValue(mergedConfig), + getActiveDatabaseConfig: vi.fn().mockReturnValue(mergedConfig.database.mysql), + getDatabaseType: vi.fn().mockReturnValue(mergedConfig.database.activeType), + getLoggingConfig: vi.fn().mockReturnValue(mergedConfig.logging), + updateConfig: vi.fn().mockResolvedValue({ success: true }), + resetToDefaults: vi.fn().mockResolvedValue(true), + getDefaultConfig: vi.fn().mockReturnValue(defaultConfig), + exportToYaml: vi.fn().mockReturnValue(''), + ...config + } as import('./types').MockConfigManager +} + +/** + * Create a mock ErpAuthService instance + * + * @param options - Options including initial login state and config + * @returns Mock ERP auth service + * + * @example + * ```typescript + * const erpAuth = createMockErpAuthService({ + * isLoggedIn: true, + * config: { url: 'https://test-erp.local' } + * }) + * ``` + */ +export function createMockErpAuthService( + options?: import('./types').MockErpAuthOptions +): import('./types').MockErpAuthService { + const isLoggedIn = options?.isLoggedIn ?? false + + const mockSession: import('./types').MockErpSession = { + browser: { + close: vi.fn().mockResolvedValue(undefined), + isConnected: vi.fn().mockReturnValue(true) + }, + context: { + close: vi.fn().mockResolvedValue(undefined), + newPage: vi.fn().mockResolvedValue(createMockPage()) + }, + page: createMockPage(), + mainFrame: createMockFrame(), + isLoggedIn + } + + return { + login: vi.fn().mockResolvedValue(mockSession), + close: vi.fn().mockResolvedValue(undefined), + getSession: vi.fn().mockReturnValue(mockSession), + isActive: vi.fn().mockReturnValue(isLoggedIn), + ...options?.overrides + } +} + +/** + * Create a mock Playwright Page instance + * + * @returns Mock page with vi.fn() implementations + */ +function createMockPage(): import('./types').MockPage { + return { + goto: vi.fn().mockResolvedValue(undefined), + waitForSelector: vi.fn().mockResolvedValue(undefined), + waitForLoadState: vi.fn().mockResolvedValue(undefined), + screenshot: vi.fn().mockResolvedValue(Buffer.from('')), + content: vi.fn().mockResolvedValue(''), + close: vi.fn().mockResolvedValue(undefined), + locator: vi.fn().mockImplementation((selector: string) => createMockLocator()), + getByRole: vi.fn().mockImplementation((role: string) => createMockLocator()) + } +} + +/** + * Create a mock Playwright Frame instance + * + * @returns Mock frame with vi.fn() implementations + */ +function createMockFrame(): import('./types').MockFrame { + return { + content: vi.fn().mockResolvedValue(''), + locator: vi.fn().mockImplementation((selector: string) => createMockLocator()), + getByRole: vi.fn().mockImplementation((role: string) => createMockLocator()), + waitForSelector: vi.fn().mockResolvedValue(undefined) + } +} + +/** + * Create a mock Playwright Locator instance + * + * @returns Mock locator with vi.fn() implementations + */ +function createMockLocator(): import('./types').MockLocator { + return { + fill: vi.fn().mockResolvedValue(undefined), + click: vi.fn().mockResolvedValue(undefined), + waitFor: vi.fn().mockResolvedValue(undefined), + isVisible: vi.fn().mockResolvedValue(false), + textContent: vi.fn().mockResolvedValue(null), + getAttribute: vi.fn().mockResolvedValue(null) + } +} + +// ============================================================================ +// Re-export existing Electron mocks from setup.ts (for convenience) +// ============================================================================ +// Note: The actual mock implementations are in tests/setup.ts +// This file provides type definitions and factory function signatures diff --git a/tests/mocks/types.ts b/tests/mocks/types.ts new file mode 100644 index 0000000..8b8d4ad --- /dev/null +++ b/tests/mocks/types.ts @@ -0,0 +1,549 @@ +/** + * Mock Type Definitions for ERPAuto Unit Tests + * + * This module provides strongly-typed Mock interfaces and factory function signatures + * for all core services that need to be mocked in unit tests. + * + * Design Principles: + * - Zero any types - all mocks are fully typed + * - Use vi.fn() mocks for all methods + * - Factory functions accept Partial overrides for customization + * - JSDoc comments on all types and functions + * + * Usage: + * - Import types from this file in test files + * - Use vi.fn() to create mock implementations + * - Factory functions provide sensible defaults + * + * Note: This file defines standalone mock types compatible with src/main interfaces. + * Import actual Config/Logger/Erp types from src/main in test files when needed. + */ + +import { vi } from 'vitest' +import type { Browser, BrowserContext, Page, Frame } from 'playwright' + +// ============================================================================ +// Re-exported/Compatible Types from src/main (for mock compatibility) +// ============================================================================ + +/** + * Logging level type - must match src/main/services/logger/index.ts + */ +export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'verbose' + +/** + * Database type enum - must match src/main/types/config.schema.ts + */ +export type DatabaseType = 'mysql' | 'sqlserver' + +/** + * MySQL configuration - compatible with src/main/types/config.schema.ts + */ +export interface MySqlConfig { + host: string + port: number + database: string + username: string + password: string + charset: string +} + +/** + * SQL Server configuration - compatible with src/main/types/config.schema.ts + */ +export interface SqlServerConfig { + server: string + port: number + database: string + username: string + password: string + driver: string + trustServerCertificate: boolean +} + +/** + * Database configuration section - compatible with src/main/types/config.schema.ts + */ +export interface DatabaseConfig { + activeType: DatabaseType + mysql: MySqlConfig + sqlserver: SqlServerConfig +} + +/** + * ERP configuration - compatible with src/main/types/config.schema.ts + */ +export interface ErpConfig { + url: string +} + +/** + * Paths configuration - compatible with src/main/types/config.schema.ts + */ +export interface PathsConfig { + dataDir: string + defaultOutput: string + validationOutput: string +} + +/** + * Extraction configuration - compatible with src/main/types/config.schema.ts + */ +export interface ExtractionConfig { + batchSize: number + verbose: boolean + autoConvert: boolean + mergeBatches: boolean + enableDbPersistence: boolean + headless: boolean +} + +/** + * Validation configuration - compatible with src/main/types/config.schema.ts + */ +export interface ValidationConfig { + dataSource: string + batchSize: number + matchMode: string + enableCrud: boolean + defaultManager: string +} + +/** + * Cleaner configuration - compatible with src/main/types/config.schema.ts + */ +export interface CleanerConfig { + queryBatchSize: number + processConcurrency: number +} + +/** + * Order resolution configuration - compatible with src/main/types/config.schema.ts + */ +export interface OrderResolutionConfig { + tableName: string + productionIdField: string + orderNumberField: string +} + +/** + * Logging configuration - compatible with src/main/types/config.schema.ts + */ +export interface LoggingConfig { + level: LogLevel + auditRetention: number + appRetention: number +} + +/** + * Seq configuration - compatible with src/main/types/config.schema.ts + */ +export interface SeqConfig { + enabled: boolean + serverUrl: string + apiKey: string + batchPostingLimit: number + period: number + queueLimit: number + maxRetries: number +} + +/** + * RustFS configuration - compatible with src/main/types/config.schema.ts + */ +export interface RustFSConfig { + enabled: boolean + endpoint: string + accessKey: string + secretKey: string + bucket: string + region: string +} + +/** + * Update configuration - compatible with src/main/types/config.schema.ts + */ +export interface UpdateConfig { + enabled: boolean + allowDevMode: boolean + endpoint: string + accessKey: string + secretKey: string + bucket: string + region: string + basePrefix: string + checkIntervalMinutes: number + maxAdminHistoryPerChannel: number +} + +/** + * Full application configuration - compatible with src/main/types/config.schema.ts + */ +export interface FullConfig { + erp: ErpConfig + database: DatabaseConfig + paths: PathsConfig + extraction: ExtractionConfig + validation: ValidationConfig + cleaner: CleanerConfig + orderResolution: OrderResolutionConfig + logging: LoggingConfig + seq: SeqConfig + rustfs: RustFSConfig + update: UpdateConfig +} + +// ============================================================================ +// Logger Mock Types +// ============================================================================ + +/** + * Mock Logger interface matching winston.Logger API + * Used for testing services that depend on logging without writing to actual log files + */ +export interface MockLogger { + /** Log at 'error' level with error serialization */ + error: (message: string, meta?: Record) => void + + /** Log at 'warn' level */ + warn: (message: string, meta?: Record) => void + + /** Log at 'info' level - most common for business logic */ + info: (message: string, meta?: Record) => void + + /** Log at 'debug' level for detailed diagnostic info */ + debug: (message: string, meta?: Record) => void + + /** Log at 'verbose' level - most detailed tracing */ + verbose: (message: string, meta?: Record) => void + + /** Create a child logger with specific context */ + child: (context: string) => MockLogger +} + +// ============================================================================ +// ConfigManager Mock Types +// ============================================================================ + +/** + * Mock ConfigManager interface matching the production ConfigManager class + * Used for testing services that depend on configuration without file I/O + */ +export interface MockConfigManager { + /** Get full configuration object */ + getConfig: () => FullConfig + + /** Get currently active database config (MySQL or SQL Server) */ + getActiveDatabaseConfig: () => MySqlConfig | SqlServerConfig + + /** Get database type enum */ + getDatabaseType: () => DatabaseType + + /** Get logging configuration section */ + getLoggingConfig: () => LoggingConfig + + /** Update configuration with deep merge */ + updateConfig: (updates: Partial) => Promise<{ success: boolean; error?: string }> + + /** Reset to default configuration */ + resetToDefaults: () => Promise + + /** Get default configuration template */ + getDefaultConfig: () => FullConfig + + /** Export config as YAML string */ + exportToYaml: () => string +} + +// ============================================================================ +// ErpAuthService Mock Types +// ============================================================================ + +/** + * Mock ErpAuthService interface matching the production ERP authentication service + * Used for testing services that interact with ERP without actual browser automation + * + * Key methods: + * - login: Establish mock ERP session + * - close: Cleanup mock session + * - getSession: Return mock session (must be logged in) + * - isActive: Check if mock session is active + */ +export interface MockErpAuthService { + /** Login to ERP system and establish mock session */ + login: () => Promise + + /** Close mock browser session and cleanup */ + close: () => Promise + + /** Get current mock session (throws if not logged in) */ + getSession: () => MockErpSession + + /** Check if mock session is active */ + isActive: () => boolean +} + +/** + * Mock ERP Session interface + * Simplified version of ErpSession for testing - uses vi.fn() mocks for Playwright objects + */ +export interface MockErpSession { + /** Mock Playwright Browser instance */ + browser: MockBrowser + + /** Mock Playwright BrowserContext instance */ + context: MockBrowserContext + + /** Mock Playwright Page instance */ + page: MockPage + + /** Mock Playwright Frame instance (forwardFrame content) */ + mainFrame: MockFrame + + /** Whether the session is logged in */ + isLoggedIn: boolean +} + +// ============================================================================ +// Playwright Mock Types +// ============================================================================ + +/** + * Mock Browser interface - simplified for unit testing + * Focus on methods used in ERPAuto codebase + */ +export interface MockBrowser { + /** Close the browser */ + close: () => Promise + + /** Check if browser is connected */ + isConnected: () => boolean +} + +/** + * Mock BrowserContext interface - simplified for unit testing + */ +export interface MockBrowserContext { + /** Close the context */ + close: () => Promise + + /** Create a new page in this context */ + newPage: () => Promise +} + +/** + * Mock Page interface - simplified for unit testing + * Includes commonly used Playwright Page methods + */ +export interface MockPage { + /** Navigate to URL */ + goto: (url: string, options?: { waitUntil?: string }) => Promise + + /** Wait for selector */ + waitForSelector: ( + selector: string, + options?: { state?: string; timeout?: number } + ) => Promise + + /** Wait for load state */ + waitForLoadState: (state: string, options?: { timeout?: number }) => Promise + + /** Take screenshot (mock - no actual file) */ + screenshot: (options?: { path?: string }) => Promise + + /** Get page content */ + content: () => Promise + + /** Close the page */ + close: () => Promise + + /** Mock locator */ + locator: (selector: string) => MockLocator + + /** Mock getByRole */ + getByRole: (role: string, options?: { name?: string }) => MockLocator +} + +/** + * Mock Frame interface - simplified for unit testing + */ +export interface MockFrame { + /** Get frame content */ + content: () => Promise + + /** Mock locator within frame */ + locator: (selector: string) => MockLocator + + /** Mock getByRole within frame */ + getByRole: (role: string, options?: { name?: string }) => MockLocator + + /** Wait for selector in frame */ + waitForSelector: ( + selector: string, + options?: { state?: string; timeout?: number } + ) => Promise +} + +/** + * Mock Locator interface - simplified for unit testing + */ +export interface MockLocator { + /** Fill input with value */ + fill: (value: string) => Promise + + /** Click the element */ + click: () => Promise + + /** Wait for element */ + waitFor: (options?: { state?: string; timeout?: number }) => Promise + + /** Check if element is visible */ + isVisible: () => Promise + + /** Get element text content */ + textContent: () => Promise + + /** Get element attribute */ + getAttribute: (name: string) => Promise +} + +// ============================================================================ +// Electron Mock Types (from setup.ts) +// ============================================================================ + +/** + * Mock Electron app interface - matches existing setup.ts implementation + */ +export interface MockElectronApp { + isPackaged: boolean + isReady: () => boolean + getPath: (name: string) => string + getVersion: () => string + getName: () => string + getAppPath: () => string + on: (event: string, listener: () => void) => void + off: (event: string, listener: () => void) => void + once: (event: string, listener: () => void) => void + emit: (event: string, ...args: unknown[]) => void + isDefaultProtocolClient: (protocol: string) => boolean + quit: () => void + relaunch: (options?: { args?: string[] }) => void + exit: (code?: number) => void + focus: () => void + blur: () => void + isQuitting: () => boolean + isAccessibilityEnabled: () => boolean + getApplicationNameForProtocol: (protocol: string) => string | null +} + +/** + * Mock IPC Main interface - matches existing setup.ts implementation + */ +export interface MockIpcMain { + handle: (channel: string, listener: (...args: unknown[]) => void | Promise) => void + on: (channel: string, listener: (...args: unknown[]) => void) => void + once: (channel: string, listener: (...args: unknown[]) => void) => void + removeHandler: (channel: string) => void + removeListener: (channel: string, listener: (...args: unknown[]) => void) => void + removeAllListeners: (channel: string) => void +} + +/** + * Mock Electron Dialog interface - matches existing setup.ts implementation + */ +export interface MockDialog { + showErrorBox: (title: string, content: string) => void + showMessageBox: (options: unknown) => Promise<{ response: number }> + showOpenDialog: (options: unknown) => Promise<{ canceled: boolean; filePaths?: string[] }> + showSaveDialog: (options: unknown) => Promise<{ canceled: boolean; filePath?: string }> +} + +/** + * Mock Electron Shell interface - matches existing setup.ts implementation + */ +export interface MockShell { + openPath: (path: string) => Promise + openExternal: (url: string) => Promise + showItemInFolder: (fullPath: string) => void + trashItem: (fullPath: string) => void +} + +/** + * Mock Electron BrowserWindow interface - matches existing setup.ts implementation + */ +export interface MockBrowserWindowConstructor { + getAllWindows: () => MockBrowserWindow[] + fromWebContents: (webContents: unknown) => MockBrowserWindow | null + fromId: (id: number) => MockBrowserWindow | null + getFocusedWindow: () => MockBrowserWindow | null +} + +/** + * Mock BrowserWindow instance interface + */ +export interface MockBrowserWindow { + isDestroyed: () => boolean + close: () => void + destroy: () => void + webContents: { + send: (channel: string, ...args: unknown[]) => void + isDestroyed: () => boolean + } +} + +// ============================================================================ +// Factory Function Type Signatures +// ============================================================================ + +/** + * Options for creating a mock logger + */ +export interface MockLoggerOptions { + /** Custom log level filters */ + level?: LogLevel + /** Override specific methods */ + overrides?: Partial +} + +/** + * Options for creating a mock config manager + */ +export interface MockConfigManagerOptions { + /** Initial config values to merge with defaults */ + config?: Partial + /** Override specific methods */ + overrides?: Partial +} + +/** + * Options for creating a mock ERP auth service + */ +export interface MockErpAuthOptions { + /** Whether the session should start as logged in */ + isLoggedIn?: boolean + /** ERP config to use */ + config?: Partial + /** Override specific methods */ + overrides?: Partial +} + +/** + * Create a mock logger instance + * @param overrides - Optional overrides for specific methods or properties + * @returns Mock logger matching winston.Logger API + */ +export type MockLoggerFactory = (overrides?: Partial) => MockLogger + +/** + * Create a mock config manager instance + * @param config - Optional partial config to use as initial state + * @returns Mock config manager + */ +export type MockConfigManagerFactory = (config?: Partial) => MockConfigManager + +/** + * Create a mock ERP auth service instance + * @param options - Options including initial login state and config + * @returns Mock ERP auth service + */ +export type MockErpAuthFactory = (options?: MockErpAuthOptions) => MockErpAuthService diff --git a/vitest.config.ts b/vitest.config.ts index c302a13..dbb429d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,6 +11,11 @@ export default defineConfig({ env: { NODE_ENV: 'test' }, + // 性能优化配置 + isolate: false, // 禁用隔离(提升 30-50% 速度) + pool: 'threads', // 使用线程池 + maxWorkers: 4, + bail: process.env.CI ? 1 : undefined, coverage: { provider: 'v8', reporter: ['text', 'json', 'html']