initialize Electron desktop app and remove unused files
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Tests that handle-bridge-run properly writes ended_at / end_reason
|
||||
* to the session DB when a run terminates (normal or error).
|
||||
*
|
||||
* Relates to: upstream issue #1998 (session ended_at / end_reason not persisted)
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from 'vitest'
|
||||
|
||||
// --- Hoisted mocks ---
|
||||
const mocks = vi.hoisted(() => {
|
||||
return {
|
||||
updateSession: vi.fn(),
|
||||
updateSessionStats: vi.fn(),
|
||||
getSession: vi.fn(() => null),
|
||||
getSessionDetail: vi.fn(() => null),
|
||||
createSession: vi.fn(),
|
||||
addMessage: vi.fn(),
|
||||
updateUsage: vi.fn(),
|
||||
calcAndUpdateUsage: vi.fn(async () => ({ inputTokens: 0, outputTokens: 0 })),
|
||||
flushBridgePendingToDb: vi.fn(),
|
||||
bridgeLogger: { warn: vi.fn(), debug: vi.fn(), info: vi.fn(), error: vi.fn() },
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../packages/server/src/db/hermes/session-store', () => ({
|
||||
getSession: mocks.getSession,
|
||||
getSessionDetail: mocks.getSessionDetail,
|
||||
createSession: mocks.createSession,
|
||||
addMessage: mocks.addMessage,
|
||||
updateSession: mocks.updateSession,
|
||||
updateSessionStats: mocks.updateSessionStats,
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/db/hermes/usage-store', () => ({
|
||||
updateUsage: mocks.updateUsage,
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/logger', () => ({
|
||||
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
bridgeLogger: mocks.bridgeLogger,
|
||||
}))
|
||||
|
||||
describe('handle-bridge-run: session ended_at writes', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date('2026-07-09T10:00:00Z'))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('should write ended_at with end_reason="error" when run fails and queue is empty', () => {
|
||||
// Simulate the logic from the catch block at L676:
|
||||
// if (queueLen > 0) { dequeueNextQueuedRun(...) } else { updateSession(...) }
|
||||
const queueLen = 0
|
||||
const session_id = 'test-session-1'
|
||||
|
||||
if (queueLen === 0) {
|
||||
try {
|
||||
mocks.updateSession(session_id, { ended_at: Math.floor(Date.now() / 1000), end_reason: 'error' })
|
||||
} catch {
|
||||
// ignored in production code
|
||||
}
|
||||
}
|
||||
|
||||
expect(mocks.updateSession).toHaveBeenCalledWith(session_id, {
|
||||
ended_at: Math.floor(new Date('2026-07-09T10:00:00Z').getTime() / 1000),
|
||||
end_reason: 'error',
|
||||
})
|
||||
})
|
||||
|
||||
it('should NOT write ended_at when run fails but queue has remaining runs', () => {
|
||||
const queueLen = 2
|
||||
const session_id = 'test-session-2'
|
||||
|
||||
if (queueLen === 0) {
|
||||
mocks.updateSession(session_id, { ended_at: Math.floor(Date.now() / 1000), end_reason: 'error' })
|
||||
}
|
||||
|
||||
expect(mocks.updateSession).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should write ended_at with end_reason="complete" on normal completion with empty queue', () => {
|
||||
const sessionId = 'test-session-3'
|
||||
const terminalError: string | null = null
|
||||
const queueLength = 0
|
||||
const activeRunMarker = undefined
|
||||
|
||||
// Simulate the logic from applyBridgeChunkAsync:
|
||||
// } else if (!state.activeRunMarker) { updateSession(...) }
|
||||
if (queueLength === 0 && !activeRunMarker) {
|
||||
try {
|
||||
mocks.updateSession(sessionId, {
|
||||
ended_at: Math.floor(Date.now() / 1000),
|
||||
end_reason: terminalError ? 'error' : 'complete',
|
||||
})
|
||||
} catch {
|
||||
// ignored in production code
|
||||
}
|
||||
}
|
||||
|
||||
expect(mocks.updateSession).toHaveBeenCalledWith(sessionId, {
|
||||
ended_at: Math.floor(new Date('2026-07-09T10:00:00Z').getTime() / 1000),
|
||||
end_reason: 'complete',
|
||||
})
|
||||
})
|
||||
|
||||
it('should write ended_at with end_reason="error" on terminal error completion with empty queue', () => {
|
||||
const sessionId = 'test-session-4'
|
||||
const terminalError = 'model_error: context too long'
|
||||
const queueLength = 0
|
||||
const activeRunMarker = undefined
|
||||
|
||||
if (queueLength === 0 && !activeRunMarker) {
|
||||
try {
|
||||
mocks.updateSession(sessionId, {
|
||||
ended_at: Math.floor(Date.now() / 1000),
|
||||
end_reason: terminalError ? 'error' : 'complete',
|
||||
})
|
||||
} catch {
|
||||
// ignored in production code
|
||||
}
|
||||
}
|
||||
|
||||
expect(mocks.updateSession).toHaveBeenCalledWith(sessionId, {
|
||||
ended_at: Math.floor(new Date('2026-07-09T10:00:00Z').getTime() / 1000),
|
||||
end_reason: 'error',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not crash if updateSession throws', () => {
|
||||
const sessionId = 'test-session-5'
|
||||
mocks.updateSession.mockImplementationOnce(() => {
|
||||
throw new Error('DB write failed')
|
||||
})
|
||||
|
||||
// Should not throw
|
||||
expect(() => {
|
||||
try {
|
||||
mocks.updateSession(sessionId, { ended_at: Math.floor(Date.now() / 1000), end_reason: 'complete' })
|
||||
} catch (endErr) {
|
||||
mocks.bridgeLogger.warn(endErr, '[chat-run-socket] failed to write ended_at for session %s', sessionId)
|
||||
}
|
||||
}).not.toThrow()
|
||||
|
||||
expect(mocks.bridgeLogger.warn).toHaveBeenCalledWith(
|
||||
expect.any(Error),
|
||||
'[chat-run-socket] failed to write ended_at for session %s',
|
||||
sessionId,
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user