From 1b0360c8c098d30e3dc614d00d17fde72024b0d1 Mon Sep 17 00:00:00 2001 From: ejclaw Date: Sun, 17 May 2026 01:57:37 +0900 Subject: [PATCH] Clear role-scoped sessions --- src/db-sessions.test.ts | 28 ++++++++++++++++++++++++++++ src/db/sessions.ts | 7 +++++-- src/runtime-state.test.ts | 37 +++++++++++++++++++++++++++++++++++++ src/runtime-state.ts | 9 ++++++++- 4 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/db-sessions.test.ts create mode 100644 src/runtime-state.test.ts diff --git a/src/db-sessions.test.ts b/src/db-sessions.test.ts new file mode 100644 index 0000000..3e2e84d --- /dev/null +++ b/src/db-sessions.test.ts @@ -0,0 +1,28 @@ +import { beforeEach, describe, expect, it } from 'vitest'; + +import { + _initTestDatabase, + deleteAllSessionsForGroup, + getSession, + setSession, +} from './db.js'; + +describe('session accessors', () => { + beforeEach(() => { + _initTestDatabase(); + }); + + it('deletes owner, reviewer, and arbiter role-scoped sessions for a group', () => { + setSession('group-a', 'owner-session'); + setSession('group-a:reviewer', 'reviewer-session'); + setSession('group-a:arbiter', 'arbiter-session'); + setSession('group-b', 'other-session'); + + deleteAllSessionsForGroup('group-a'); + + expect(getSession('group-a')).toBeUndefined(); + expect(getSession('group-a:reviewer')).toBeUndefined(); + expect(getSession('group-a:arbiter')).toBeUndefined(); + expect(getSession('group-b')).toBe('other-session'); + }); +}); diff --git a/src/db/sessions.ts b/src/db/sessions.ts index e834992..07bf82a 100644 --- a/src/db/sessions.ts +++ b/src/db/sessions.ts @@ -141,8 +141,11 @@ export function deleteAllSessionsForGroupFromDatabase( groupFolder: string, ): void { database - .prepare('DELETE FROM sessions WHERE group_folder = ?') - .run(groupFolder); + .prepare( + `DELETE FROM sessions + WHERE group_folder IN (?, ?, ?)`, + ) + .run(groupFolder, `${groupFolder}:reviewer`, `${groupFolder}:arbiter`); } export function getAllSessionsForAgentTypeFromDatabase( diff --git a/src/runtime-state.test.ts b/src/runtime-state.test.ts new file mode 100644 index 0000000..85323ce --- /dev/null +++ b/src/runtime-state.test.ts @@ -0,0 +1,37 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const db = vi.hoisted(() => ({ + assignRoom: vi.fn(), + deleteAllSessionsForGroup: vi.fn(), + deleteSession: vi.fn(), + getAllRoomBindings: vi.fn(() => ({})), + getAllSessions: vi.fn(() => ({})), + getRouterState: vi.fn(() => ''), + setRouterState: vi.fn(), + setSession: vi.fn(), +})); + +vi.mock('./db.js', () => db); + +import { createRuntimeState } from './runtime-state.js'; + +describe('createRuntimeState', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('clears role-scoped sessions from memory when allRoles is requested', () => { + const state = createRuntimeState(); + + state.persistSession('group-a', 'owner-session'); + state.persistSession('group-a:reviewer', 'reviewer-session'); + state.persistSession('group-a:arbiter', 'arbiter-session'); + state.persistSession('group-b', 'other-session'); + + state.clearSession('group-a', { allRoles: true }); + + expect(state.getSessions()).toEqual({ 'group-b': 'other-session' }); + expect(db.deleteAllSessionsForGroup).toHaveBeenCalledWith('group-a'); + expect(db.deleteSession).not.toHaveBeenCalled(); + }); +}); diff --git a/src/runtime-state.ts b/src/runtime-state.ts index 536811c..597588e 100644 --- a/src/runtime-state.ts +++ b/src/runtime-state.ts @@ -48,10 +48,17 @@ export function createRuntimeState(): RuntimeState { groupFolder: string, opts?: { allRoles?: boolean }, ): void => { - delete sessions[groupFolder]; if (opts?.allRoles) { + for (const key of [ + groupFolder, + `${groupFolder}:reviewer`, + `${groupFolder}:arbiter`, + ]) { + delete sessions[key]; + } deleteAllSessionsForGroup(groupFolder); } else { + delete sessions[groupFolder]; deleteSession(groupFolder); } };