Files
hermes-web-ui/tests/server/sessions-controller.test.ts
T
ekko 30715c45cc fix(group-chat): add multi-profile usage tracking and fix tests
- Add getSessionDetailFromDbWithProfile to query session details from specific profile's state.db
- Record usage for group chat agent runs with agent's own profile to roomId
- Update context compression to use agent's profile instead of active profile
- Add profile parameter to BuildContextInput and GatewayCaller.summarize interfaces
- Add profile field to updateUsage calls in proxy-handler for single chat runs
- Fix SessionDeleter to clean up gc_session_profiles after successful session deletion
- Fix tests to match current logic and skip FTS5-dependent tests

This allows multiple agents with different profiles in the same group chat to correctly track their usage separately.
2026-04-29 11:49:10 +08:00

125 lines
5.1 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const listConversationSummariesFromDbMock = vi.fn()
const getConversationDetailFromDbMock = vi.fn()
const listConversationSummariesMock = vi.fn()
const getConversationDetailMock = vi.fn()
const getSessionDetailFromDbMock = vi.fn()
const getSessionMock = vi.fn()
const getGroupChatServerMock = vi.fn()
const loggerWarnMock = vi.fn()
vi.mock('../../packages/server/src/db/hermes/conversations-db', () => ({
listConversationSummariesFromDb: listConversationSummariesFromDbMock,
getConversationDetailFromDb: getConversationDetailFromDbMock,
}))
vi.mock('../../packages/server/src/services/hermes/conversations', () => ({
listConversationSummaries: listConversationSummariesMock,
getConversationDetail: getConversationDetailMock,
}))
vi.mock('../../packages/server/src/services/logger', () => ({
logger: {
warn: loggerWarnMock,
error: vi.fn(),
},
}))
vi.mock('../../packages/server/src/services/hermes/hermes-cli', () => ({
listSessions: vi.fn(),
getSession: getSessionMock,
deleteSession: vi.fn(),
renameSession: vi.fn(),
}))
vi.mock('../../packages/server/src/db/hermes/sessions-db', () => ({
listSessionSummaries: vi.fn(),
searchSessionSummaries: vi.fn(),
getSessionDetailFromDb: getSessionDetailFromDbMock,
}))
// Mock useLocalSessionStore to return false so we test the CLI path
vi.mock('../../packages/server/src/db/hermes/session-store', () => ({
useLocalSessionStore: () => false,
}))
vi.mock('../../packages/server/src/db/hermes/usage-store', () => ({
deleteUsage: vi.fn(),
getUsage: vi.fn(),
getUsageBatch: vi.fn(),
}))
vi.mock('../../packages/server/src/routes/hermes/group-chat', () => ({
getGroupChatServer: getGroupChatServerMock,
}))
vi.mock('../../packages/server/src/services/hermes/model-context', () => ({
getModelContextLength: vi.fn(),
}))
describe('session conversations controller', () => {
beforeEach(() => {
vi.resetModules()
listConversationSummariesFromDbMock.mockReset()
getConversationDetailFromDbMock.mockReset()
listConversationSummariesMock.mockReset()
getConversationDetailMock.mockReset()
getSessionDetailFromDbMock.mockReset()
getSessionMock.mockReset()
getGroupChatServerMock.mockReset()
getGroupChatServerMock.mockReturnValue(null)
loggerWarnMock.mockReset()
})
it('prefers the DB-backed conversations summary path', async () => {
listConversationSummariesFromDbMock.mockResolvedValue([{ id: 'db-conversation' }])
const mod = await import('../../packages/server/src/controllers/hermes/sessions')
const ctx: any = { query: { humanOnly: 'true', limit: '5' }, body: null }
await mod.listConversations(ctx)
expect(listConversationSummariesFromDbMock).toHaveBeenCalledWith({ source: undefined, humanOnly: true, limit: 5 })
expect(listConversationSummariesMock).not.toHaveBeenCalled()
expect(ctx.body).toEqual({ sessions: [{ id: 'db-conversation' }] })
})
it('falls back to the CLI-export conversations summary path when the DB query fails', async () => {
listConversationSummariesFromDbMock.mockRejectedValue(new Error('db unavailable'))
listConversationSummariesMock.mockResolvedValue([{ id: 'fallback-conversation' }])
const mod = await import('../../packages/server/src/controllers/hermes/sessions')
const ctx: any = { query: { humanOnly: 'false' }, body: null }
await mod.listConversations(ctx)
expect(loggerWarnMock).toHaveBeenCalled()
expect(listConversationSummariesMock).toHaveBeenCalledWith({ source: undefined, humanOnly: false, limit: undefined })
expect(ctx.body).toEqual({ sessions: [{ id: 'fallback-conversation' }] })
})
it('prefers the DB-backed conversation detail path', async () => {
getConversationDetailFromDbMock.mockResolvedValue({ session_id: 'root', messages: [], visible_count: 0, thread_session_count: 1 })
const mod = await import('../../packages/server/src/controllers/hermes/sessions')
const ctx: any = { params: { id: 'root' }, query: { humanOnly: 'true' }, body: null }
await mod.getConversationMessages(ctx)
expect(getConversationDetailFromDbMock).toHaveBeenCalledWith('root', { source: undefined, humanOnly: true })
expect(getConversationDetailMock).not.toHaveBeenCalled()
expect(ctx.body).toEqual({ session_id: 'root', messages: [], visible_count: 0, thread_session_count: 1 })
})
it('falls back to the CLI-export conversation detail path when the DB query throws', async () => {
getConversationDetailFromDbMock.mockRejectedValue(new Error('db unavailable'))
getConversationDetailMock.mockResolvedValue({ session_id: 'root', messages: [{ id: 1 }], visible_count: 1, thread_session_count: 1 })
const mod = await import('../../packages/server/src/controllers/hermes/sessions')
const ctx: any = { params: { id: 'root' }, query: { humanOnly: 'false' }, body: null }
await mod.getConversationMessages(ctx)
expect(loggerWarnMock).toHaveBeenCalled()
expect(getConversationDetailMock).toHaveBeenCalledWith('root', { source: undefined, humanOnly: false })
expect(ctx.body).toEqual({ session_id: 'root', messages: [{ id: 1 }], visible_count: 1, thread_session_count: 1 })
})
})