style: apply formatter to docs, types, and test files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-04-05 11:56:22 +08:00
parent 9791a84047
commit e54d94fce2
13 changed files with 444 additions and 358 deletions

View File

@@ -21,16 +21,20 @@ describe('Cleaner Service (Integration)', () => {
const hasCredentials = !!(config.url && config.username && config.password)
describe('Dry-run mode', () => {
it.skipIf(!hasCredentials)('should initialize with dry-run mode', async () => {
const authService = new ErpAuthService(config)
await authService.login()
it.skipIf(!hasCredentials)(
'should initialize with dry-run mode',
async () => {
const authService = new ErpAuthService(config)
await authService.login()
const cleaner = new CleanerService(authService, { dryRun: true })
const cleaner = new CleanerService(authService, { dryRun: true })
expect(cleaner.isDryRun()).toBe(true)
expect(cleaner.isDryRun()).toBe(true)
await authService.close()
}, 30000)
await authService.close()
},
30000
)
it.skipIf(!hasCredentials)(
'should track materials to delete without actually deleting (dry-run)',
@@ -83,115 +87,131 @@ describe('Cleaner Service (Integration)', () => {
})
describe('Order processing', () => {
it.skipIf(!hasCredentials)('should process single order and return details', async () => {
const authService = new ErpAuthService(config)
await authService.login()
it.skipIf(!hasCredentials)(
'should process single order and return details',
async () => {
const authService = new ErpAuthService(config)
await authService.login()
const orderContent = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = orderContent
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.slice(0, 1) // Test single order
const orderContent = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = orderContent
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.slice(0, 1) // Test single order
const cleaner = new CleanerService(authService, { dryRun: true })
const cleaner = new CleanerService(authService, { dryRun: true })
const result = await cleaner.clean({
orderNumbers,
materialCodes: [], // Empty list - nothing to delete
dryRun: true
})
const result = await cleaner.clean({
orderNumbers,
materialCodes: [], // Empty list - nothing to delete
dryRun: true
})
expect(result.ordersProcessed).toBe(1)
expect(result.details).toHaveLength(1)
expect(result.details[0].orderNumber).toBe(orderNumbers[0])
expect(result.ordersProcessed).toBe(1)
expect(result.details).toHaveLength(1)
expect(result.details[0].orderNumber).toBe(orderNumbers[0])
await authService.close()
}, 60000)
await authService.close()
},
60000
)
it.skipIf(!hasCredentials)('should handle order with "审批通过" status', async () => {
const authService = new ErpAuthService(config)
await authService.login()
it.skipIf(!hasCredentials)(
'should handle order with "审批通过" status',
async () => {
const authService = new ErpAuthService(config)
await authService.login()
const orderContent = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = orderContent
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.slice(0, 1)
const orderContent = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = orderContent
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.slice(0, 1)
const cleaner = new CleanerService(authService, { dryRun: true })
const cleaner = new CleanerService(authService, { dryRun: true })
const result = await cleaner.clean({
orderNumbers,
materialCodes: [],
dryRun: true
})
const result = await cleaner.clean({
orderNumbers,
materialCodes: [],
dryRun: true
})
// Order details should include status information
const detail = result.details[0]
console.log(
`Order ${detail.orderNumber} - Materials deleted: ${detail.materialsDeleted}, Skipped: ${detail.materialsSkipped}`
)
// Order details should include status information
const detail = result.details[0]
console.log(
`Order ${detail.orderNumber} - Materials deleted: ${detail.materialsDeleted}, Skipped: ${detail.materialsSkipped}`
)
expect(detail).toBeDefined()
expect(detail).toBeDefined()
await authService.close()
}, 60000)
await authService.close()
},
60000
)
it.skipIf(!hasCredentials)('should handle multiple orders with progress callback', async () => {
const authService = new ErpAuthService(config)
await authService.login()
it.skipIf(!hasCredentials)(
'should handle multiple orders with progress callback',
async () => {
const authService = new ErpAuthService(config)
await authService.login()
const orderContent = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = orderContent
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.slice(0, 3) // Test 3 orders
const orderContent = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = orderContent
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.slice(0, 3) // Test 3 orders
const progressMessages: string[] = []
const progressMessages: string[] = []
const cleaner = new CleanerService(authService, { dryRun: true })
const cleaner = new CleanerService(authService, { dryRun: true })
const result = await cleaner.clean({
orderNumbers,
materialCodes: [],
dryRun: true,
onProgress: (message, progress) => {
progressMessages.push(`${progress?.toFixed(0)}%: ${message}`)
}
})
const result = await cleaner.clean({
orderNumbers,
materialCodes: [],
dryRun: true,
onProgress: (message, progress) => {
progressMessages.push(`${progress?.toFixed(0)}%: ${message}`)
}
})
expect(result.ordersProcessed).toBe(3)
expect(progressMessages.length).toBeGreaterThan(0)
expect(result.ordersProcessed).toBe(3)
expect(progressMessages.length).toBeGreaterThan(0)
console.log('Progress messages:', progressMessages.slice(0, 5))
console.log('Progress messages:', progressMessages.slice(0, 5))
await authService.close()
}, 180000)
await authService.close()
},
180000
)
})
describe('Error handling', () => {
it.skipIf(!hasCredentials)('should continue processing after order error', async () => {
const authService = new ErpAuthService(config)
await authService.login()
it.skipIf(!hasCredentials)(
'should continue processing after order error',
async () => {
const authService = new ErpAuthService(config)
await authService.login()
const orderNumbers = ['INVALID_ORDER_12345', 'INVALID_ORDER_67890']
const orderNumbers = ['INVALID_ORDER_12345', 'INVALID_ORDER_67890']
const cleaner = new CleanerService(authService, { dryRun: true })
const cleaner = new CleanerService(authService, { dryRun: true })
const result = await cleaner.clean({
orderNumbers,
materialCodes: [],
dryRun: true
})
const result = await cleaner.clean({
orderNumbers,
materialCodes: [],
dryRun: true
})
// Should still process (even if with errors)
expect(result.details.length).toBeGreaterThan(0)
// Should still process (even if with errors)
expect(result.details.length).toBeGreaterThan(0)
await authService.close()
}, 120000)
await authService.close()
},
120000
)
})
describe('Navigation', () => {

View File

@@ -19,22 +19,30 @@ describe('ERP Authentication Service (Integration)', () => {
authService = new ErpAuthService(config)
})
it.skipIf(!hasCredentials)('should login successfully', async () => {
const session = await authService.login()
it.skipIf(!hasCredentials)(
'should login successfully',
async () => {
const session = await authService.login()
expect(session).toBeDefined()
expect(session.browser).toBeDefined()
expect(session.context).toBeDefined()
expect(session.page).toBeDefined()
expect(session.isLoggedIn).toBe(true)
}, 30000)
expect(session).toBeDefined()
expect(session.browser).toBeDefined()
expect(session.context).toBeDefined()
expect(session.page).toBeDefined()
expect(session.isLoggedIn).toBe(true)
},
30000
)
it.skipIf(!hasCredentials)('should navigate to main page after login', async () => {
const session = await authService.login()
it.skipIf(!hasCredentials)(
'should navigate to main page after login',
async () => {
const session = await authService.login()
const url = session.page.url()
expect(url).toContain(config.url)
}, 30000)
const url = session.page.url()
expect(url).toContain(config.url)
},
30000
)
afterAll(async () => {
if (hasCredentials && authService) {

View File

@@ -18,117 +18,129 @@ describe('Extractor Service (Integration)', () => {
// Check if we have ERP credentials
const hasCredentials = !!(config.url && config.username && config.password)
it.skipIf(!hasCredentials)('should extract data for single order number', async () => {
// Create fresh auth service for this test
const authService = new ErpAuthService(config)
await authService.login()
it.skipIf(!hasCredentials)(
'should extract data for single order number',
async () => {
// Create fresh auth service for this test
const authService = new ErpAuthService(config)
await authService.login()
const extractor = new ExtractorService(authService)
const extractor = new ExtractorService(authService)
const result = await extractor.extract({
orderNumbers: [testOrderNumber]
})
const result = await extractor.extract({
orderNumbers: [testOrderNumber]
})
expect(result.downloadedFiles).toHaveLength(1)
expect(result.errors).toHaveLength(0)
expect(result.downloadedFiles).toHaveLength(1)
expect(result.errors).toHaveLength(0)
// Verify file exists
const filePath = result.downloadedFiles[0]
const stats = await fs.stat(filePath)
expect(stats.size).toBeGreaterThan(0)
// Clean up
await authService.close()
}, 60000)
it.skipIf(!hasCredentials)('should extract data for multiple order numbers', async () => {
// Create fresh auth service for this test
const authService = new ErpAuthService(config)
await authService.login()
const extractor = new ExtractorService(authService)
// Read order numbers from productionID.txt file
const productionIdFile = path.join(process.cwd(), '../references/demo/productionID.txt')
const content = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = content
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.slice(0, 5) // Test first 5 orders
console.log(`Testing with ${orderNumbers.length} order numbers:`, orderNumbers)
const result = await extractor.extract({
orderNumbers,
batchSize: 100 // Process all in one batch
})
console.log(`Downloaded ${result.downloadedFiles.length} files`)
if (result.errors.length > 0) {
console.log('Errors:', result.errors)
}
expect(result.downloadedFiles.length).toBeGreaterThanOrEqual(1)
// Clean up
await authService.close()
}, 120000) // Increase timeout to 2 minutes
it.skipIf(!hasCredentials)('should extract data for 300 orders with batch size 70', async () => {
// Create fresh auth service for this test
const authService = new ErpAuthService(config)
await authService.login()
const extractor = new ExtractorService(authService)
// Read all order numbers from productionID.txt file
const productionIdFile = path.join(process.cwd(), '../references/demo/productionID.txt')
const content = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = content
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
console.log(`Testing with ${orderNumbers.length} order numbers`)
console.log(`Batch size: 70, Expected batches: ${Math.ceil(orderNumbers.length / 70)}`)
const startTime = Date.now()
const result = await extractor.extract({
orderNumbers,
batchSize: 70 // Process 70 orders per batch
})
const endTime = Date.now()
const duration = ((endTime - startTime) / 1000).toFixed(2)
console.log(`\n=== Extraction Summary ===`)
console.log(`Total orders: ${orderNumbers.length}`)
console.log(`Batch size: 70`)
console.log(`Expected batches: ${Math.ceil(orderNumbers.length / 70)}`)
console.log(`Downloaded files: ${result.downloadedFiles.length}`)
console.log(`Total duration: ${duration}s`)
console.log(
`Average time per batch: ${(duration / result.downloadedFiles.length).toFixed(2)}s`
)
if (result.errors.length > 0) {
console.log(`\nErrors encountered: ${result.errors.length}`)
result.errors.forEach((err, idx) => console.log(` ${idx + 1}. ${err}`))
}
// Verify results
expect(result.downloadedFiles.length).toBeGreaterThanOrEqual(1)
// Verify each downloaded file exists and has content
for (const filePath of result.downloadedFiles) {
// Verify file exists
const filePath = result.downloadedFiles[0]
const stats = await fs.stat(filePath)
console.log(` - ${path.basename(filePath)}: ${(stats.size / 1024).toFixed(2)} KB`)
expect(stats.size).toBeGreaterThan(0)
}
// Clean up
await authService.close()
}, 600000) // 10 minutes timeout for large batch test
// Clean up
await authService.close()
},
60000
)
it.skipIf(!hasCredentials)(
'should extract data for multiple order numbers',
async () => {
// Create fresh auth service for this test
const authService = new ErpAuthService(config)
await authService.login()
const extractor = new ExtractorService(authService)
// Read order numbers from productionID.txt file
const productionIdFile = path.join(process.cwd(), '../references/demo/productionID.txt')
const content = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = content
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.slice(0, 5) // Test first 5 orders
console.log(`Testing with ${orderNumbers.length} order numbers:`, orderNumbers)
const result = await extractor.extract({
orderNumbers,
batchSize: 100 // Process all in one batch
})
console.log(`Downloaded ${result.downloadedFiles.length} files`)
if (result.errors.length > 0) {
console.log('Errors:', result.errors)
}
expect(result.downloadedFiles.length).toBeGreaterThanOrEqual(1)
// Clean up
await authService.close()
},
120000
) // Increase timeout to 2 minutes
it.skipIf(!hasCredentials)(
'should extract data for 300 orders with batch size 70',
async () => {
// Create fresh auth service for this test
const authService = new ErpAuthService(config)
await authService.login()
const extractor = new ExtractorService(authService)
// Read all order numbers from productionID.txt file
const productionIdFile = path.join(process.cwd(), '../references/demo/productionID.txt')
const content = await fs.readFile(productionIdFile, 'utf-8')
const orderNumbers = content
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
console.log(`Testing with ${orderNumbers.length} order numbers`)
console.log(`Batch size: 70, Expected batches: ${Math.ceil(orderNumbers.length / 70)}`)
const startTime = Date.now()
const result = await extractor.extract({
orderNumbers,
batchSize: 70 // Process 70 orders per batch
})
const endTime = Date.now()
const duration = ((endTime - startTime) / 1000).toFixed(2)
console.log(`\n=== Extraction Summary ===`)
console.log(`Total orders: ${orderNumbers.length}`)
console.log(`Batch size: 70`)
console.log(`Expected batches: ${Math.ceil(orderNumbers.length / 70)}`)
console.log(`Downloaded files: ${result.downloadedFiles.length}`)
console.log(`Total duration: ${duration}s`)
console.log(
`Average time per batch: ${(duration / result.downloadedFiles.length).toFixed(2)}s`
)
if (result.errors.length > 0) {
console.log(`\nErrors encountered: ${result.errors.length}`)
result.errors.forEach((err, idx) => console.log(` ${idx + 1}. ${err}`))
}
// Verify results
expect(result.downloadedFiles.length).toBeGreaterThanOrEqual(1)
// Verify each downloaded file exists and has content
for (const filePath of result.downloadedFiles) {
const stats = await fs.stat(filePath)
console.log(` - ${path.basename(filePath)}: ${(stats.size / 1024).toFixed(2)} KB`)
expect(stats.size).toBeGreaterThan(0)
}
// Clean up
await authService.close()
},
600000
) // 10 minutes timeout for large batch test
})

View File

@@ -689,11 +689,12 @@ export function createMockRepository(options?: {
return {
find: vi.fn().mockResolvedValue(mockFindResult),
findOne: vi.fn().mockResolvedValue(mockFindResult[0] ?? null),
create: vi.fn((plainObject?: Record<string, unknown>) => plainObject ?? ({})),
create: vi.fn((plainObject?: Record<string, unknown>) => plainObject ?? {}),
save: vi.fn().mockImplementation((entity: Record<string, unknown>) => Promise.resolve(entity)),
delete: vi.fn().mockResolvedValue({ affected: 1 }),
count: vi.fn().mockResolvedValue(mockFindResult.length),
createQueryBuilder: vi .fn()
createQueryBuilder: vi
.fn()
.mockImplementation(() => createMockQueryBuilder({ result: mockFindResult }))
}
}
@@ -724,9 +725,12 @@ export function createMockDataSource(
destroy: vi.fn().mockResolvedValue(undefined),
isInitialized,
getRepository: vi.fn().mockReturnValue(mockRepo),
create: vi.fn().mockImplementation(
(_entityClass: unknown, plainObject?: Record<string, unknown>) => plainObject ?? ({} as Record<string, unknown>)
),
create: vi
.fn()
.mockImplementation(
(_entityClass: unknown, plainObject?: Record<string, unknown>) =>
plainObject ?? ({} as Record<string, unknown>)
),
save: vi.fn().mockImplementation((entity: Record<string, unknown>) => Promise.resolve(entity)),
createQueryBuilder: vi
.fn()

View File

@@ -20,9 +20,7 @@ describe('MySqlDialect', () => {
})
it('should handle arbitrary schema and table names', () => {
expect(dialect.quoteTableName('my_schema', 'my_table')).toBe(
'my_schema_my_table'
)
expect(dialect.quoteTableName('my_schema', 'my_table')).toBe('my_schema_my_table')
})
})

View File

@@ -20,9 +20,7 @@ describe('PostgreSqlDialect', () => {
})
it('should handle arbitrary names', () => {
expect(dialect.quoteTableName('my_schema', 'my_table')).toBe(
'"my_schema"."my_table"'
)
expect(dialect.quoteTableName('my_schema', 'my_table')).toBe('"my_schema"."my_table"')
})
})

View File

@@ -20,9 +20,7 @@ describe('SqlServerDialect', () => {
})
it('should handle arbitrary names', () => {
expect(dialect.quoteTableName('my_schema', 'my_table')).toBe(
'[my_schema].[my_table]'
)
expect(dialect.quoteTableName('my_schema', 'my_table')).toBe('[my_schema].[my_table]')
})
})

View File

@@ -74,16 +74,13 @@ vi.mock('../../src/main/services/database/data-source', () => ({
// MaterialsToBeDeletedRepository
// ---------------------------------------------------------------------------
describe('MaterialsToBeDeletedRepository', () => {
let MaterialsToBeDeletedRepository: typeof import(
'../../src/main/services/database/repositories/MaterialsToBeDeletedRepository'
).MaterialsToBeDeletedRepository
let MaterialsToBeDeletedRepository: typeof import('../../src/main/services/database/repositories/MaterialsToBeDeletedRepository').MaterialsToBeDeletedRepository
beforeEach(async () => {
vi.clearAllMocks()
mockRepo = createMockRepository()
const mod = await import(
'../../src/main/services/database/repositories/MaterialsToBeDeletedRepository'
)
const mod =
await import('../../src/main/services/database/repositories/MaterialsToBeDeletedRepository')
MaterialsToBeDeletedRepository = mod.MaterialsToBeDeletedRepository
})
@@ -384,16 +381,13 @@ describe('MaterialsToBeDeletedRepository', () => {
// DiscreteMaterialPlanRepository
// ---------------------------------------------------------------------------
describe('DiscreteMaterialPlanRepository', () => {
let DiscreteMaterialPlanRepository: typeof import(
'../../src/main/services/database/repositories/DiscreteMaterialPlanRepository'
).DiscreteMaterialPlanRepository
let DiscreteMaterialPlanRepository: typeof import('../../src/main/services/database/repositories/DiscreteMaterialPlanRepository').DiscreteMaterialPlanRepository
beforeEach(async () => {
vi.clearAllMocks()
mockRepo = createMockRepository()
const mod = await import(
'../../src/main/services/database/repositories/DiscreteMaterialPlanRepository'
)
const mod =
await import('../../src/main/services/database/repositories/DiscreteMaterialPlanRepository')
DiscreteMaterialPlanRepository = mod.DiscreteMaterialPlanRepository
})
@@ -430,9 +424,8 @@ describe('DiscreteMaterialPlanRepository', () => {
mockRepo = { ...createMockRepository(), query: vi.fn().mockResolvedValue(mockQueryResult) }
// Re-import to pick up new mockRepo
vi.resetModules()
const mod = await import(
'../../src/main/services/database/repositories/DiscreteMaterialPlanRepository'
)
const mod =
await import('../../src/main/services/database/repositories/DiscreteMaterialPlanRepository')
const freshRepo = new mod.DiscreteMaterialPlanRepository()
const result = await freshRepo.queryAllDistinctByMaterialCode()
@@ -447,9 +440,8 @@ describe('DiscreteMaterialPlanRepository', () => {
query: vi.fn().mockRejectedValue(new Error('db fail'))
}
vi.resetModules()
const mod = await import(
'../../src/main/services/database/repositories/DiscreteMaterialPlanRepository'
)
const mod =
await import('../../src/main/services/database/repositories/DiscreteMaterialPlanRepository')
const freshRepo = new mod.DiscreteMaterialPlanRepository()
const result = await freshRepo.queryAllDistinctByMaterialCode()
@@ -512,9 +504,8 @@ describe('DiscreteMaterialPlanRepository', () => {
it('queryBySourceNumbersDistinct: calls repo.query() per batch', async () => {
mockRepo = { ...createMockRepository(), query: vi.fn().mockResolvedValue([{ M: 'X' }]) }
vi.resetModules()
const mod = await import(
'../../src/main/services/database/repositories/DiscreteMaterialPlanRepository'
)
const mod =
await import('../../src/main/services/database/repositories/DiscreteMaterialPlanRepository')
const repo = new mod.DiscreteMaterialPlanRepository()
const result = await repo.queryBySourceNumbersDistinct(['S1', 'S2'])
@@ -529,9 +520,8 @@ describe('DiscreteMaterialPlanRepository', () => {
query: vi.fn().mockRejectedValue(new Error('db fail'))
}
vi.resetModules()
const mod = await import(
'../../src/main/services/database/repositories/DiscreteMaterialPlanRepository'
)
const mod =
await import('../../src/main/services/database/repositories/DiscreteMaterialPlanRepository')
const repo = new mod.DiscreteMaterialPlanRepository()
const result = await repo.queryBySourceNumbersDistinct(['S1'])