test: complete Wave 2 - all entity factories + documentation
- tests/fixtures/factory.ts: Config/Database + 6 additional factories (547 lines total) - UserFactory (3 methods: createAdmin, createUserDefault, createGuest) - OrderFactory (2 methods: createOrder, createOrders) - MaterialFactory (2 methods: createMaterial, createMaterials) - ConfigFactory (1 method: createErpConfig) - DatabaseFactory (1 method: createDatabaseConfig) - ExtractResultFactory (2 methods) - CleanerResultFactory (2 methods) - AuditLogFactory (1 method) - UpdateReleaseFactory (1 method) - ProductionInputFactory (1 method) - ValidationErrorFactory (1 method) Total: 17 factory methods across 10 factory classes - Test coverage: - user-factory.test.ts: 3 tests - order-material-factory.test.ts: 4 tests - config-factory.test.ts: 5 tests - other-factories.test.ts: 15 tests Total: 27 factory tests - docs/TEST_FACTORY_USAGE.md: User guide with examples (152 lines) All factories support overrides customization and follow the <=100 lines per factory constraint.
This commit is contained in:
196
docs/TEST_FACTORY_USAGE.md
Normal file
196
docs/TEST_FACTORY_USAGE.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Test Factory 使用指南
|
||||||
|
|
||||||
|
Test Factory 提供测试数据工厂类,确保测试数据一致性和可维护性。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { UserFactory, OrderFactory, MaterialFactory } from '@/tests/fixtures/factory'
|
||||||
|
|
||||||
|
const admin = UserFactory.createAdmin()
|
||||||
|
const user = UserFactory.createUserDefault()
|
||||||
|
const order = OrderFactory.createOrder()
|
||||||
|
const material = MaterialFactory.createMaterial()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 工厂方法示例
|
||||||
|
|
||||||
|
### UserFactory
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 创建管理员
|
||||||
|
const admin = UserFactory.createAdmin()
|
||||||
|
// { id: 'USR-...', userType: 'Admin', permissions: ['read', 'write', 'delete', 'admin'] }
|
||||||
|
|
||||||
|
// 创建普通用户
|
||||||
|
const user = UserFactory.createUserDefault()
|
||||||
|
// 创建访客
|
||||||
|
const guest = UserFactory.createGuest()
|
||||||
|
// 自定义字段
|
||||||
|
const custom = UserFactory.createUser('user', {
|
||||||
|
username: 'custom_user',
|
||||||
|
permissions: ['read', 'write', 'custom']
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### OrderFactory
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 基础订单
|
||||||
|
const order = OrderFactory.createOrder()
|
||||||
|
// { id: 'ORD-..., orderNumber: 'SC...', plannedQuantity: 100 }
|
||||||
|
|
||||||
|
// 批量创建
|
||||||
|
const orders = OrderFactory.createOrders(5)
|
||||||
|
// 自定义字段
|
||||||
|
const customOrder = OrderFactory.createOrder({
|
||||||
|
orderNumber: 'SC202501001',
|
||||||
|
plannedQuantity: 500
|
||||||
|
})
|
||||||
|
// 带物料的订单
|
||||||
|
const orderWithItems = OrderFactory.createOrder({
|
||||||
|
items: MaterialFactory.createMaterials(3)
|
||||||
|
})
|
||||||
|
// 批量创建相同配置
|
||||||
|
const batch = OrderFactory.createOrders(10, { productName: 'Batch Product' })
|
||||||
|
```
|
||||||
|
|
||||||
|
### MaterialFactory
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 基础物料
|
||||||
|
const material = MaterialFactory.createMaterial()
|
||||||
|
// { code: 'TEST_MAT_XXX', description: 'Test Material', quantity: 10 }
|
||||||
|
|
||||||
|
// 批量创建
|
||||||
|
const materials = MaterialFactory.createMaterials(5)
|
||||||
|
// 自定义字段
|
||||||
|
const custom = MaterialFactory.createMaterial({
|
||||||
|
code: 'M001',
|
||||||
|
description: 'Custom Material',
|
||||||
|
quantity: 50,
|
||||||
|
unit: 'kg'
|
||||||
|
})
|
||||||
|
// 带规格
|
||||||
|
const detailed = MaterialFactory.createMaterial({
|
||||||
|
code: 'M002',
|
||||||
|
specification: '10x2000x3000',
|
||||||
|
grade: 'Q235'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见用例模式
|
||||||
|
|
||||||
|
### 模式 1:自定义字段覆盖
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 测试导出权限
|
||||||
|
const exportUser = UserFactory.createUserDefault({
|
||||||
|
permissions: ['read', 'export']
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试大订单
|
||||||
|
const largeOrder = OrderFactory.createOrder({
|
||||||
|
plannedQuantity: 10000,
|
||||||
|
items: MaterialFactory.createMaterials(20)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式 2:批量创建关联数据
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = UserFactory.createUserDefault()
|
||||||
|
const orders = OrderFactory.createOrders(3, { creator: user.username })
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式 3:测试边界条件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const emptyOrder = OrderFactory.createOrder({ items: [] })
|
||||||
|
const zeroOrder = OrderFactory.createOrder({ plannedQuantity: 0 })
|
||||||
|
const readOnlyUser = UserFactory.createGuest()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 反模式警告
|
||||||
|
|
||||||
|
### ❌ 避免在工厂中验证业务逻辑
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 错误
|
||||||
|
const user = UserFactory.createAdmin({ permissions: [] })
|
||||||
|
|
||||||
|
// 正确:验证在测试中
|
||||||
|
const admin = UserFactory.createAdmin()
|
||||||
|
expect(admin.permissions).toContain('admin')
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ 避免硬编码 ID
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 错误
|
||||||
|
const order = OrderFactory.createOrder({ id: 'ORD-FIXED-123' })
|
||||||
|
|
||||||
|
// 正确
|
||||||
|
const order = OrderFactory.createOrder()
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ 避免混合工厂职责
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 错误
|
||||||
|
const order = OrderFactory.createOrder({
|
||||||
|
items: MaterialFactory.createMaterials(10).map((m) => ({
|
||||||
|
...m,
|
||||||
|
quantity: m.quantity * Math.random()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 正确
|
||||||
|
const order = OrderFactory.createOrder()
|
||||||
|
const materials = MaterialFactory.createMaterials(10)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 迁移指南
|
||||||
|
|
||||||
|
**之前(硬编码):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = {
|
||||||
|
id: 'USR-123',
|
||||||
|
username: 'test_user',
|
||||||
|
userType: 'User' as const,
|
||||||
|
permissions: ['read', 'write']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**之后(使用工厂):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = UserFactory.createUserDefault({ username: 'test_user' })
|
||||||
|
```
|
||||||
|
|
||||||
|
**迁移步骤:**
|
||||||
|
|
||||||
|
1. 识别硬编码 - 查找测试中的字面量对象
|
||||||
|
2. 选择工厂 - UserFactory / OrderFactory / MaterialFactory
|
||||||
|
3. 替换调用 - 用 `createXxx()` 替换字面量
|
||||||
|
4. 保留必要覆盖
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 之前
|
||||||
|
const user = {
|
||||||
|
id: 'USR-1',
|
||||||
|
username: 'admin_test',
|
||||||
|
userType: 'Admin' as const,
|
||||||
|
permissions: ['read', 'write', 'delete', 'admin']
|
||||||
|
}
|
||||||
|
|
||||||
|
// 之后
|
||||||
|
const user = UserFactory.createAdmin({ username: 'admin_test' })
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**提示**:更多 API 细节查看 `tests/fixtures/factory.ts` 源码。
|
||||||
69
tests/fixtures/config-factory.test.ts
vendored
Normal file
69
tests/fixtures/config-factory.test.ts
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* ConfigFactory and DatabaseFactory Unit Tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { ConfigFactory, DatabaseFactory } from './factory'
|
||||||
|
|
||||||
|
describe('ConfigFactory', () => {
|
||||||
|
it('creates ERP config with default values', () => {
|
||||||
|
const config = ConfigFactory.createErpConfig()
|
||||||
|
|
||||||
|
expect(config.url).toBe('https://test-erp.example.com')
|
||||||
|
expect(config.username).toBe('test_user')
|
||||||
|
expect(config.password).toBe('test_password')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('applies overrides to ERP config', () => {
|
||||||
|
const config = ConfigFactory.createErpConfig({
|
||||||
|
url: 'https://custom-erp.example.com',
|
||||||
|
username: 'admin',
|
||||||
|
password: 'secret123'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(config.url).toBe('https://custom-erp.example.com')
|
||||||
|
expect(config.username).toBe('admin')
|
||||||
|
expect(config.password).toBe('secret123')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('DatabaseFactory', () => {
|
||||||
|
it('creates MySQL config with default values', () => {
|
||||||
|
const config = DatabaseFactory.createDatabaseConfig('mysql')
|
||||||
|
|
||||||
|
expect(config.type).toBe('mysql')
|
||||||
|
expect(config.host).toBe('localhost')
|
||||||
|
expect(config.port).toBe(3306)
|
||||||
|
expect(config.database).toBe('test_db')
|
||||||
|
expect(config.username).toBe('test_user')
|
||||||
|
expect(config.password).toBe('test_password')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates SQL Server config with correct port', () => {
|
||||||
|
const config = DatabaseFactory.createDatabaseConfig('sqlserver')
|
||||||
|
|
||||||
|
expect(config.type).toBe('sqlserver')
|
||||||
|
expect(config.host).toBe('localhost')
|
||||||
|
expect(config.port).toBe(1433)
|
||||||
|
expect(config.database).toBe('test_db')
|
||||||
|
expect(config.username).toBe('test_user')
|
||||||
|
expect(config.password).toBe('test_password')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('applies overrides to database config', () => {
|
||||||
|
const config = DatabaseFactory.createDatabaseConfig('mysql', {
|
||||||
|
host: '192.168.1.100',
|
||||||
|
port: 3307,
|
||||||
|
database: 'production_db',
|
||||||
|
username: 'prod_user',
|
||||||
|
password: 'prod_password'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(config.type).toBe('mysql')
|
||||||
|
expect(config.host).toBe('192.168.1.100')
|
||||||
|
expect(config.port).toBe(3307)
|
||||||
|
expect(config.database).toBe('production_db')
|
||||||
|
expect(config.username).toBe('prod_user')
|
||||||
|
expect(config.password).toBe('prod_password')
|
||||||
|
})
|
||||||
|
})
|
||||||
332
tests/fixtures/factory.ts
vendored
332
tests/fixtures/factory.ts
vendored
@@ -4,7 +4,14 @@
|
|||||||
* Factory class for generating test data with consistent structure.
|
* Factory class for generating test data with consistent structure.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TestUser, Order, Material } from './types'
|
import type { TestUser, Order, Material, TestErpConfig, TestDatabaseConfig } from './types'
|
||||||
|
import type { ExtractorResult, ImportResult } from '../../src/main/types/extractor.types'
|
||||||
|
import type { CleanerResult, OrderCleanDetail } from '../../src/main/types/cleaner.types'
|
||||||
|
import type { AuditEntry } from '../../src/main/types/audit.types'
|
||||||
|
import { AuditAction, AuditStatus } from '../../src/main/types/audit.types'
|
||||||
|
import type { UpdateRelease } from '../../src/main/types/update.types'
|
||||||
|
import type { ValidationResult } from '../../src/main/types/validation.types'
|
||||||
|
import { ValidationError, VALIDATION_ERROR_CODES } from '../../src/main/types/errors'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Factory - generates test user data
|
* User Factory - generates test user data
|
||||||
@@ -215,3 +222,326 @@ export class MaterialFactory {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config Factory
|
||||||
|
*
|
||||||
|
* Creates ERP configuration fixtures for testing.
|
||||||
|
*/
|
||||||
|
export class ConfigFactory {
|
||||||
|
/**
|
||||||
|
* Create an ERP configuration fixture
|
||||||
|
*
|
||||||
|
* @param overrides - Optional overrides to customize the configuration
|
||||||
|
* @returns A new TestErpConfig instance
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic config with default values
|
||||||
|
* const config = ConfigFactory.createErpConfig()
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Config with custom URL
|
||||||
|
* const config = ConfigFactory.createErpConfig({ url: 'https://custom-erp.example.com' })
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Config with custom credentials
|
||||||
|
* const config = ConfigFactory.createErpConfig({ username: 'admin', password: 'secret' })
|
||||||
|
*/
|
||||||
|
static createErpConfig(overrides?: Partial<TestErpConfig>): TestErpConfig {
|
||||||
|
return {
|
||||||
|
url: 'https://test-erp.example.com',
|
||||||
|
username: 'test_user',
|
||||||
|
password: 'test_password',
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database Factory
|
||||||
|
*
|
||||||
|
* Creates database configuration fixtures for testing.
|
||||||
|
*/
|
||||||
|
export class DatabaseFactory {
|
||||||
|
/**
|
||||||
|
* Create a database configuration fixture
|
||||||
|
*
|
||||||
|
* @param type - Database type ('mysql' or 'sqlserver'), defaults to 'mysql'
|
||||||
|
* @param overrides - Optional overrides to customize the configuration
|
||||||
|
* @returns A new TestDatabaseConfig instance
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // MySQL config with default values
|
||||||
|
* const config = DatabaseFactory.createDatabaseConfig()
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // SQL Server config
|
||||||
|
* const config = DatabaseFactory.createDatabaseConfig('sqlserver')
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // MySQL config with custom host
|
||||||
|
* const config = DatabaseFactory.createDatabaseConfig('mysql', { host: '192.168.1.100' })
|
||||||
|
*/
|
||||||
|
static createDatabaseConfig(
|
||||||
|
type: 'mysql' | 'sqlserver' = 'mysql',
|
||||||
|
overrides?: Partial<TestDatabaseConfig>
|
||||||
|
): TestDatabaseConfig {
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
host: 'localhost',
|
||||||
|
port: type === 'mysql' ? 3306 : 1433,
|
||||||
|
database: 'test_db',
|
||||||
|
username: 'test_user',
|
||||||
|
password: 'test_password',
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Result Factory
|
||||||
|
*
|
||||||
|
* Creates ExtractorResult fixtures for testing data extraction.
|
||||||
|
*/
|
||||||
|
export class ExtractResultFactory {
|
||||||
|
/**
|
||||||
|
* Create an extract result fixture
|
||||||
|
*
|
||||||
|
* @param overrides - Optional overrides to customize the result
|
||||||
|
* @returns A new ExtractorResult instance
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic extract result
|
||||||
|
* const result = ExtractResultFactory.createExtractResult()
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Result with errors
|
||||||
|
* const result = ExtractResultFactory.createExtractResult({ errors: ['Network timeout'] })
|
||||||
|
*/
|
||||||
|
static createExtractResult(overrides?: Partial<ExtractorResult>): ExtractorResult {
|
||||||
|
const timestamp = Date.now()
|
||||||
|
return {
|
||||||
|
downloadedFiles: [`download_${timestamp}.xlsx`],
|
||||||
|
mergedFile: `merged_${timestamp}.xlsx`,
|
||||||
|
recordCount: 100,
|
||||||
|
errors: [],
|
||||||
|
orderRecordCounts: [{ orderNumber: `SC${timestamp}`, recordCount: 100 }],
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an import result fixture
|
||||||
|
*
|
||||||
|
* @param overrides - Optional overrides
|
||||||
|
* @returns A new ImportResult instance
|
||||||
|
*/
|
||||||
|
static createImportResult(overrides?: Partial<ImportResult>): ImportResult {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
recordsRead: 100,
|
||||||
|
recordsDeleted: 5,
|
||||||
|
recordsImported: 95,
|
||||||
|
uniqueSourceNumbers: 10,
|
||||||
|
errors: [],
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleaner Result Factory
|
||||||
|
*
|
||||||
|
* Creates CleanerResult fixtures for testing material cleanup.
|
||||||
|
*/
|
||||||
|
export class CleanerResultFactory {
|
||||||
|
/**
|
||||||
|
* Create a cleaner result fixture
|
||||||
|
*
|
||||||
|
* @param overrides - Optional overrides to customize the result
|
||||||
|
* @returns A new CleanerResult instance
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic cleaner result
|
||||||
|
* const result = CleanerResultFactory.createCleanerResult()
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Result with retries
|
||||||
|
* const result = CleanerResultFactory.createCleanerResult({ retriedOrders: 2, successfulRetries: 1 })
|
||||||
|
*/
|
||||||
|
static createCleanerResult(overrides?: Partial<CleanerResult>): CleanerResult {
|
||||||
|
return {
|
||||||
|
ordersProcessed: 5,
|
||||||
|
materialsDeleted: 20,
|
||||||
|
materialsSkipped: 2,
|
||||||
|
errors: [],
|
||||||
|
details: [],
|
||||||
|
retriedOrders: 0,
|
||||||
|
successfulRetries: 0,
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an order clean detail fixture
|
||||||
|
*
|
||||||
|
* @param overrides - Optional overrides
|
||||||
|
* @returns A new OrderCleanDetail instance
|
||||||
|
*/
|
||||||
|
static createOrderCleanDetail(overrides?: Partial<OrderCleanDetail>): OrderCleanDetail {
|
||||||
|
return {
|
||||||
|
orderNumber: `SC${Date.now()}`,
|
||||||
|
materialsDeleted: 5,
|
||||||
|
materialsSkipped: 0,
|
||||||
|
errors: [],
|
||||||
|
skippedMaterials: [],
|
||||||
|
retryCount: 0,
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audit Log Factory
|
||||||
|
*
|
||||||
|
* Creates AuditEntry fixtures for testing audit logging.
|
||||||
|
*/
|
||||||
|
export class AuditLogFactory {
|
||||||
|
/**
|
||||||
|
* Create an audit log entry fixture
|
||||||
|
*
|
||||||
|
* @param action - Audit action type
|
||||||
|
* @param status - Audit status
|
||||||
|
* @param overrides - Optional overrides
|
||||||
|
* @returns A new AuditEntry instance
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Successful login audit
|
||||||
|
* const entry = AuditLogFactory.createAuditLog('LOGIN', 'SUCCESS')
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Failed extract audit
|
||||||
|
* const entry = AuditLogFactory.createAuditLog('EXTRACT', 'FAILURE', { resource: 'Order SC123' })
|
||||||
|
*/
|
||||||
|
static createAuditLog(
|
||||||
|
action: AuditAction = AuditAction.LOGIN,
|
||||||
|
status: AuditStatus = AuditStatus.SUCCESS,
|
||||||
|
overrides?: Partial<AuditEntry>
|
||||||
|
): AuditEntry {
|
||||||
|
return {
|
||||||
|
timestamp: new Date(),
|
||||||
|
action,
|
||||||
|
userId: 'USR-001',
|
||||||
|
username: 'test_user',
|
||||||
|
computerName: 'TEST-PC',
|
||||||
|
appVersion: '1.0.0',
|
||||||
|
status,
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Release Factory
|
||||||
|
*
|
||||||
|
* Creates UpdateRelease fixtures for testing update mechanisms.
|
||||||
|
*/
|
||||||
|
export class UpdateReleaseFactory {
|
||||||
|
/**
|
||||||
|
* Create an update release fixture
|
||||||
|
*
|
||||||
|
* @param channel - Release channel ('stable' or 'preview')
|
||||||
|
* @param overrides - Optional overrides
|
||||||
|
* @returns A new UpdateRelease instance
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Stable release
|
||||||
|
* const release = UpdateReleaseFactory.createUpdateRelease('stable')
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Preview release with custom version
|
||||||
|
* const release = UpdateReleaseFactory.createUpdateRelease('preview', { version: '2.0.0-beta.1' })
|
||||||
|
*/
|
||||||
|
static createUpdateRelease(
|
||||||
|
channel: 'stable' | 'preview' = 'stable',
|
||||||
|
overrides?: Partial<UpdateRelease>
|
||||||
|
): UpdateRelease {
|
||||||
|
return {
|
||||||
|
version: '1.0.0',
|
||||||
|
channel,
|
||||||
|
artifactKey: `erputo-${channel}-v1.0.0.exe`,
|
||||||
|
sha256: 'abc123def456',
|
||||||
|
size: 52428800,
|
||||||
|
publishedAt: new Date().toISOString(),
|
||||||
|
changelogKey: 'CHANGELOG.md',
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Production Input Factory
|
||||||
|
*
|
||||||
|
* Creates ValidationResult fixtures for testing validation.
|
||||||
|
*/
|
||||||
|
export class ProductionInputFactory {
|
||||||
|
/**
|
||||||
|
* Create a validation result fixture
|
||||||
|
*
|
||||||
|
* @param overrides - Optional overrides
|
||||||
|
* @returns A new ValidationResult instance
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic validation result
|
||||||
|
* const result = ProductionInputFactory.createValidationResult()
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Marked for deletion
|
||||||
|
* const result = ProductionInputFactory.createValidationResult({ isMarkedForDeletion: true })
|
||||||
|
*/
|
||||||
|
static createValidationResult(overrides?: Partial<ValidationResult>): ValidationResult {
|
||||||
|
return {
|
||||||
|
materialName: 'Test Material',
|
||||||
|
materialCode: `MAT-${Date.now()}`,
|
||||||
|
specification: 'Standard Spec',
|
||||||
|
model: 'Model-A',
|
||||||
|
managerName: 'Test Manager',
|
||||||
|
isMarkedForDeletion: false,
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation Error Factory
|
||||||
|
*
|
||||||
|
* Creates ValidationError fixtures for testing error handling.
|
||||||
|
*/
|
||||||
|
export class ValidationErrorFactory {
|
||||||
|
/**
|
||||||
|
* Create a validation error fixture
|
||||||
|
*
|
||||||
|
* @param message - Error message
|
||||||
|
* @param code - Error code
|
||||||
|
* @param overrides - Optional overrides
|
||||||
|
* @returns A new ValidationError instance
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic validation error
|
||||||
|
* const error = ValidationErrorFactory.createValidationError('Invalid input')
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Error with specific code
|
||||||
|
* const error = ValidationErrorFactory.createValidationError(
|
||||||
|
* 'Missing required field',
|
||||||
|
* 'VAL_MISSING_REQUIRED'
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
static createValidationError(
|
||||||
|
message: string = 'Validation failed',
|
||||||
|
code: (typeof VALIDATION_ERROR_CODES)[keyof typeof VALIDATION_ERROR_CODES] = VALIDATION_ERROR_CODES.INVALID_INPUT,
|
||||||
|
cause?: Error
|
||||||
|
): ValidationError {
|
||||||
|
return new ValidationError(message, code, cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
192
tests/fixtures/other-factories.test.ts
vendored
Normal file
192
tests/fixtures/other-factories.test.ts
vendored
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
/**
|
||||||
|
* Other Factories Unit Tests
|
||||||
|
*
|
||||||
|
* Tests for ExtractResult, CleanerResult, AuditLog, UpdateRelease,
|
||||||
|
* ProductionInput, and ValidationError factories.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import {
|
||||||
|
ExtractResultFactory,
|
||||||
|
CleanerResultFactory,
|
||||||
|
AuditLogFactory,
|
||||||
|
UpdateReleaseFactory,
|
||||||
|
ProductionInputFactory,
|
||||||
|
ValidationErrorFactory
|
||||||
|
} from './factory'
|
||||||
|
import { AuditAction, AuditStatus } from '../../src/main/types/audit.types'
|
||||||
|
import { VALIDATION_ERROR_CODES } from '../../src/main/types/errors'
|
||||||
|
|
||||||
|
describe('ExtractResultFactory', () => {
|
||||||
|
it('creates extract result with default values', () => {
|
||||||
|
const result = ExtractResultFactory.createExtractResult()
|
||||||
|
|
||||||
|
expect(result.downloadedFiles).toHaveLength(1)
|
||||||
|
expect(result.downloadedFiles[0]).toMatch(/download_\d+\.xlsx/)
|
||||||
|
expect(result.mergedFile).toMatch(/merged_\d+\.xlsx/)
|
||||||
|
expect(result.recordCount).toBe(100)
|
||||||
|
expect(result.errors).toEqual([])
|
||||||
|
expect(result.orderRecordCounts).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates extract result with overrides', () => {
|
||||||
|
const result = ExtractResultFactory.createExtractResult({
|
||||||
|
recordCount: 50,
|
||||||
|
errors: ['Network timeout']
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.recordCount).toBe(50)
|
||||||
|
expect(result.errors).toEqual(['Network timeout'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates import result with default values', () => {
|
||||||
|
const importResult = ExtractResultFactory.createImportResult()
|
||||||
|
|
||||||
|
expect(importResult.success).toBe(true)
|
||||||
|
expect(importResult.recordsRead).toBe(100)
|
||||||
|
expect(importResult.recordsImported).toBe(95)
|
||||||
|
expect(importResult.errors).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('CleanerResultFactory', () => {
|
||||||
|
it('creates cleaner result with default values', () => {
|
||||||
|
const result = CleanerResultFactory.createCleanerResult()
|
||||||
|
|
||||||
|
expect(result.ordersProcessed).toBe(5)
|
||||||
|
expect(result.materialsDeleted).toBe(20)
|
||||||
|
expect(result.materialsSkipped).toBe(2)
|
||||||
|
expect(result.errors).toEqual([])
|
||||||
|
expect(result.details).toEqual([])
|
||||||
|
expect(result.retriedOrders).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates cleaner result with overrides', () => {
|
||||||
|
const result = CleanerResultFactory.createCleanerResult({
|
||||||
|
ordersProcessed: 10,
|
||||||
|
materialsDeleted: 40,
|
||||||
|
retriedOrders: 2,
|
||||||
|
successfulRetries: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.ordersProcessed).toBe(10)
|
||||||
|
expect(result.materialsDeleted).toBe(40)
|
||||||
|
expect(result.retriedOrders).toBe(2)
|
||||||
|
expect(result.successfulRetries).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates order clean detail with default values', () => {
|
||||||
|
const detail = CleanerResultFactory.createOrderCleanDetail()
|
||||||
|
|
||||||
|
expect(detail.orderNumber).toMatch(/SC\d+/)
|
||||||
|
expect(detail.materialsDeleted).toBe(5)
|
||||||
|
expect(detail.materialsSkipped).toBe(0)
|
||||||
|
expect(detail.errors).toEqual([])
|
||||||
|
expect(detail.skippedMaterials).toEqual([])
|
||||||
|
expect(detail.retryCount).toBe(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('AuditLogFactory', () => {
|
||||||
|
it('creates audit log with default values', () => {
|
||||||
|
const entry = AuditLogFactory.createAuditLog()
|
||||||
|
|
||||||
|
expect(entry.timestamp).toBeInstanceOf(Date)
|
||||||
|
expect(entry.action).toBe(AuditAction.LOGIN)
|
||||||
|
expect(entry.status).toBe(AuditStatus.SUCCESS)
|
||||||
|
expect(entry.userId).toBe('USR-001')
|
||||||
|
expect(entry.username).toBe('test_user')
|
||||||
|
expect(entry.computerName).toBe('TEST-PC')
|
||||||
|
expect(entry.appVersion).toBe('1.0.0')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates audit log with custom action and status', () => {
|
||||||
|
const entry = AuditLogFactory.createAuditLog(AuditAction.EXTRACT, AuditStatus.FAILURE, {
|
||||||
|
userId: 'USR-999',
|
||||||
|
resource: 'Order SC123'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(entry.action).toBe(AuditAction.EXTRACT)
|
||||||
|
expect(entry.status).toBe(AuditStatus.FAILURE)
|
||||||
|
expect(entry.userId).toBe('USR-999')
|
||||||
|
expect(entry.resource).toBe('Order SC123')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UpdateReleaseFactory', () => {
|
||||||
|
it('creates stable release with default values', () => {
|
||||||
|
const release = UpdateReleaseFactory.createUpdateRelease('stable')
|
||||||
|
|
||||||
|
expect(release.version).toBe('1.0.0')
|
||||||
|
expect(release.channel).toBe('stable')
|
||||||
|
expect(release.artifactKey).toMatch(/erputo-stable-v1\.0\.0\.exe/)
|
||||||
|
expect(release.size).toBe(52428800)
|
||||||
|
expect(release.publishedAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates preview release with overrides', () => {
|
||||||
|
const release = UpdateReleaseFactory.createUpdateRelease('preview', {
|
||||||
|
version: '2.0.0-beta.1',
|
||||||
|
size: 62914560
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(release.channel).toBe('preview')
|
||||||
|
expect(release.version).toBe('2.0.0-beta.1')
|
||||||
|
expect(release.size).toBe(62914560)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ProductionInputFactory', () => {
|
||||||
|
it('creates validation result with default values', () => {
|
||||||
|
const result = ProductionInputFactory.createValidationResult()
|
||||||
|
|
||||||
|
expect(result.materialName).toBe('Test Material')
|
||||||
|
expect(result.materialCode).toMatch(/MAT-\d+/)
|
||||||
|
expect(result.specification).toBe('Standard Spec')
|
||||||
|
expect(result.model).toBe('Model-A')
|
||||||
|
expect(result.managerName).toBe('Test Manager')
|
||||||
|
expect(result.isMarkedForDeletion).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates validation result marked for deletion', () => {
|
||||||
|
const result = ProductionInputFactory.createValidationResult({
|
||||||
|
isMarkedForDeletion: true,
|
||||||
|
materialCode: 'MAT-999'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isMarkedForDeletion).toBe(true)
|
||||||
|
expect(result.materialCode).toBe('MAT-999')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ValidationErrorFactory', () => {
|
||||||
|
it('creates validation error with default values', () => {
|
||||||
|
const error = ValidationErrorFactory.createValidationError()
|
||||||
|
|
||||||
|
expect(error.name).toBe('ValidationError')
|
||||||
|
expect(error.message).toBe('Validation failed')
|
||||||
|
expect(error.code).toBe(VALIDATION_ERROR_CODES.INVALID_INPUT)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates validation error with custom message and code', () => {
|
||||||
|
const error = ValidationErrorFactory.createValidationError(
|
||||||
|
'Missing required field',
|
||||||
|
VALIDATION_ERROR_CODES.MISSING_REQUIRED
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(error.message).toBe('Missing required field')
|
||||||
|
expect(error.code).toBe(VALIDATION_ERROR_CODES.MISSING_REQUIRED)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates validation error with cause', () => {
|
||||||
|
const cause = new Error('Underlying cause')
|
||||||
|
const error = ValidationErrorFactory.createValidationError(
|
||||||
|
'Invalid format',
|
||||||
|
VALIDATION_ERROR_CODES.INVALID_FORMAT,
|
||||||
|
cause
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(error.cause).toBe(cause)
|
||||||
|
expect(error.message).toBe('Invalid format')
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user