fix: restore cleaner history in postgresql
This commit is contained in:
29
tests/unit/cleaner-history-load-state.test.ts
Normal file
29
tests/unit/cleaner-history-load-state.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
canStartHistoryLoad,
|
||||
getNextHistoryLoadState
|
||||
} from '../../src/renderer/src/components/cleaner-history-load-state'
|
||||
|
||||
describe('cleaner history load state helpers', () => {
|
||||
it('allows initial and failed loads to retry', () => {
|
||||
expect(canStartHistoryLoad('idle')).toBe(true)
|
||||
expect(canStartHistoryLoad('error')).toBe(true)
|
||||
})
|
||||
|
||||
it('prevents duplicate requests after loading starts or succeeds', () => {
|
||||
expect(canStartHistoryLoad('loading')).toBe(false)
|
||||
expect(canStartHistoryLoad('success')).toBe(false)
|
||||
})
|
||||
|
||||
it('retries after an error but keeps successful loads cached', () => {
|
||||
const failedState = getNextHistoryLoadState('loading', 'error')
|
||||
const retryState = getNextHistoryLoadState(failedState, 'start')
|
||||
const successState = getNextHistoryLoadState(retryState, 'success')
|
||||
const blockedState = getNextHistoryLoadState(successState, 'start')
|
||||
|
||||
expect(failedState).toBe('error')
|
||||
expect(retryState).toBe('loading')
|
||||
expect(successState).toBe('success')
|
||||
expect(blockedState).toBe('success')
|
||||
})
|
||||
})
|
||||
107
tests/unit/cleaner-page-history-lazy.test.tsx
Normal file
107
tests/unit/cleaner-page-history-lazy.test.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import React from 'react'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
||||
|
||||
const { mockHistoryModalModuleLoad } = vi.hoisted(() => ({
|
||||
mockHistoryModalModuleLoad: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../../src/renderer/src/hooks/useCleaner', () => ({
|
||||
useCleaner: () => ({
|
||||
isAdmin: false,
|
||||
currentUsername: 'tester',
|
||||
dryRun: false,
|
||||
setDryRun: vi.fn(),
|
||||
valMode: 'database_full',
|
||||
setValMode: vi.fn(),
|
||||
validationResults: [],
|
||||
selectedItems: new Set<string>(),
|
||||
setSelectedItems: vi.fn(),
|
||||
setHiddenItems: vi.fn(),
|
||||
managers: [],
|
||||
selectedManagers: [],
|
||||
setSelectedManagers: vi.fn(),
|
||||
isRunning: false,
|
||||
isExecuting: false,
|
||||
isValidationRunning: false,
|
||||
isExporting: false,
|
||||
isTypeDialogOpen: false,
|
||||
setIsTypeDialogOpen: vi.fn(),
|
||||
headless: false,
|
||||
setHeadless: vi.fn(),
|
||||
processConcurrency: 1,
|
||||
updateProcessConcurrency: vi.fn(),
|
||||
showSettingsMenu: false,
|
||||
setShowSettingsMenu: vi.fn(),
|
||||
filteredResults: [],
|
||||
isReportDialogOpen: false,
|
||||
setIsReportDialogOpen: vi.fn(),
|
||||
reportData: null,
|
||||
editingCell: null,
|
||||
editValue: '',
|
||||
setEditValue: vi.fn(),
|
||||
inputRef: { current: null },
|
||||
startEdit: vi.fn(),
|
||||
saveEdit: vi.fn(),
|
||||
cancelEdit: vi.fn(),
|
||||
handleAssignManagerOnSelect: vi.fn(),
|
||||
progress: null,
|
||||
startTime: null,
|
||||
resetStartTime: vi.fn(),
|
||||
handleValidation: vi.fn(),
|
||||
handleCheckboxToggle: vi.fn(),
|
||||
handleConfirmDeletion: vi.fn(),
|
||||
handleExecuteDeletion: vi.fn(),
|
||||
handleExportResults: vi.fn(),
|
||||
confirmDialog: null
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('../../src/renderer/src/components/cleaner/CleanerExecutionBar', () => ({
|
||||
CleanerExecutionBar: () => React.createElement('div', null, 'execution-bar')
|
||||
}))
|
||||
|
||||
vi.mock('../../src/renderer/src/components/cleaner/CleanerResultsTable', () => ({
|
||||
CleanerResultsTable: () => React.createElement('div', null, 'results-table')
|
||||
}))
|
||||
|
||||
vi.mock('../../src/renderer/src/components/cleaner/CleanerSidebar', () => ({
|
||||
CleanerSidebar: () => React.createElement('aside', null, 'sidebar')
|
||||
}))
|
||||
|
||||
vi.mock('../../src/renderer/src/components/cleaner/CleanerToolbar', () => ({
|
||||
CleanerToolbar: () => React.createElement('div', null, 'toolbar')
|
||||
}))
|
||||
|
||||
vi.mock('../../src/renderer/src/components/ui/ConfirmDialog', () => ({
|
||||
ConfirmDialog: () => React.createElement('div', null, 'confirm-dialog')
|
||||
}))
|
||||
|
||||
vi.mock('../../src/renderer/src/components/MaterialTypeManagementDialog', () => ({
|
||||
default: () => React.createElement('div', null, 'type-dialog')
|
||||
}))
|
||||
|
||||
vi.mock('../../src/renderer/src/components/ExecutionReportDialog', () => ({
|
||||
default: () => React.createElement('div', null, 'report-dialog')
|
||||
}))
|
||||
|
||||
vi.mock('../../src/renderer/src/components/CleanerOperationHistoryModal', () => {
|
||||
mockHistoryModalModuleLoad()
|
||||
return {
|
||||
default: () => React.createElement('div', null, 'history-dialog')
|
||||
}
|
||||
})
|
||||
|
||||
import CleanerPage from '../../src/renderer/src/pages/CleanerPage'
|
||||
|
||||
describe('CleanerPage history modal lazy loading', () => {
|
||||
beforeEach(() => {
|
||||
mockHistoryModalModuleLoad.mockClear()
|
||||
})
|
||||
|
||||
it('does not load the history modal module on the initial render', () => {
|
||||
renderToStaticMarkup(React.createElement(CleanerPage))
|
||||
|
||||
expect(mockHistoryModalModuleLoad).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,108 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const queryMock = vi.fn()
|
||||
const createMock = vi.fn()
|
||||
const trackDurationMock = vi.fn(async (fn: () => Promise<unknown>) => ({ result: await fn() }))
|
||||
|
||||
vi.mock('../../../../src/main/services/logger', () => ({
|
||||
createLogger: () => ({
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn()
|
||||
}),
|
||||
getRequestId: () => 'test-request-id',
|
||||
trackDuration: trackDurationMock
|
||||
}))
|
||||
|
||||
vi.mock('../../../../src/main/services/database/index', () => ({
|
||||
create: createMock
|
||||
}))
|
||||
|
||||
describe('CleanerOperationHistoryDAO (PostgreSQL compatibility)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
createMock.mockResolvedValue({
|
||||
type: 'postgresql',
|
||||
isConnected: () => true,
|
||||
query: queryMock,
|
||||
disconnect: vi.fn()
|
||||
})
|
||||
})
|
||||
|
||||
it('uses PostgreSQL-compatible aggregation in getBatches', async () => {
|
||||
queryMock.mockResolvedValue({
|
||||
rows: [],
|
||||
columns: [],
|
||||
rowCount: 0
|
||||
})
|
||||
|
||||
const { CleanerOperationHistoryDAO } = await import(
|
||||
'../../../../src/main/services/database/cleaner-operation-history-dao'
|
||||
)
|
||||
const dao = new CleanerOperationHistoryDAO()
|
||||
|
||||
await dao.getBatches(undefined, { limit: 10 })
|
||||
|
||||
expect(queryMock).toHaveBeenCalledTimes(1)
|
||||
const sql = queryMock.mock.calls[0][0] as string
|
||||
|
||||
expect(sql).toContain('COALESCE(SUM(CASE WHEN o.Status = \'success\' THEN 1 ELSE 0 END), 0)')
|
||||
expect(sql).toContain('COALESCE(SUM(CASE WHEN o.Status = \'failed\' THEN 1 ELSE 0 END), 0)')
|
||||
expect(sql).toContain('MAX(CASE WHEN e.IsDryRun THEN 1 ELSE 0 END) as IsDryRun')
|
||||
expect(sql).not.toContain('ISNULL(')
|
||||
})
|
||||
|
||||
it('avoids SQL Server TOP syntax when checking delete permissions', async () => {
|
||||
queryMock
|
||||
.mockResolvedValueOnce({
|
||||
rows: [
|
||||
{
|
||||
ID: 1,
|
||||
BatchId: 'batch-1',
|
||||
AttemptNumber: 1,
|
||||
UserId: 7,
|
||||
Username: 'tester',
|
||||
OperationTime: new Date('2026-04-14T10:00:00.000Z'),
|
||||
EndTime: null,
|
||||
Status: 'success',
|
||||
IsDryRun: false,
|
||||
TotalOrders: 1,
|
||||
OrdersProcessed: 1,
|
||||
TotalMaterialsDeleted: 1,
|
||||
TotalMaterialsSkipped: 0,
|
||||
TotalMaterialsFailed: 0,
|
||||
TotalUncertainDeletions: 0,
|
||||
ErrorMessage: null,
|
||||
AppVersion: '1.12.3'
|
||||
}
|
||||
],
|
||||
columns: [],
|
||||
rowCount: 1
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
rows: [],
|
||||
columns: [],
|
||||
rowCount: 0
|
||||
})
|
||||
.mockResolvedValue({
|
||||
rows: [],
|
||||
columns: [],
|
||||
rowCount: 1
|
||||
})
|
||||
|
||||
const { CleanerOperationHistoryDAO } = await import(
|
||||
'../../../../src/main/services/database/cleaner-operation-history-dao'
|
||||
)
|
||||
const dao = new CleanerOperationHistoryDAO()
|
||||
|
||||
const result = await dao.deleteBatch('batch-1', 7, false)
|
||||
|
||||
expect(result).toEqual({ success: true })
|
||||
const executedSql = queryMock.mock.calls.map(([sql]) => sql as string).join('\n')
|
||||
expect(executedSql).not.toContain('TOP 1')
|
||||
expect(executedSql).toContain('FROM "ERPAuto"."CleanerExecution"')
|
||||
expect(executedSql).toContain('DELETE FROM "ERPAuto"."CleanerMaterialDetail"')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user