300 lines
8.3 KiB
TypeScript
300 lines
8.3 KiB
TypeScript
import fs from 'fs';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
|
|
import { Database } from 'bun:sqlite';
|
|
|
|
import {
|
|
detectRoomRegistrationState,
|
|
getRoomRegistrationGateFailure,
|
|
} from './room-registration-state.js';
|
|
|
|
describe('room registration state', () => {
|
|
const tempRoots: string[] = [];
|
|
|
|
afterEach(() => {
|
|
for (const root of tempRoots.splice(0)) {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('reports no assigned rooms when the database does not exist', () => {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
|
|
tempRoots.push(tempRoot);
|
|
|
|
expect(
|
|
detectRoomRegistrationState({
|
|
projectRoot: tempRoot,
|
|
dbPath: path.join(tempRoot, 'messages.db'),
|
|
}),
|
|
).toEqual({
|
|
assignedRooms: 0,
|
|
roomsByOwnerAgent: {},
|
|
legacyRegisteredGroupRows: 0,
|
|
legacyRoomMigrationRequired: false,
|
|
unexpectedDataStateFiles: [],
|
|
unexpectedDataStateDetected: false,
|
|
});
|
|
});
|
|
|
|
it('counts canonical room_settings rows and owner-agent breakdowns', () => {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
|
|
tempRoots.push(tempRoot);
|
|
const dbPath = path.join(tempRoot, 'messages.db');
|
|
const db = new Database(dbPath);
|
|
db.exec(`
|
|
CREATE TABLE room_settings (
|
|
chat_jid TEXT PRIMARY KEY,
|
|
room_mode TEXT NOT NULL,
|
|
owner_agent_type TEXT,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
`);
|
|
db.exec(`
|
|
INSERT INTO room_settings (chat_jid, room_mode, owner_agent_type, updated_at) VALUES
|
|
('group-1', 'single', 'claude-code', '2024-01-01T00:00:00.000Z'),
|
|
('group-2', 'single', 'codex', '2024-01-01T00:00:00.000Z'),
|
|
('group-3', 'tribunal', 'codex', '2024-01-01T00:00:00.000Z');
|
|
`);
|
|
db.close();
|
|
|
|
expect(
|
|
detectRoomRegistrationState({ projectRoot: tempRoot, dbPath }),
|
|
).toEqual({
|
|
assignedRooms: 3,
|
|
roomsByOwnerAgent: {
|
|
'claude-code': 1,
|
|
codex: 2,
|
|
},
|
|
legacyRegisteredGroupRows: 0,
|
|
legacyRoomMigrationRequired: false,
|
|
unexpectedDataStateFiles: [],
|
|
unexpectedDataStateDetected: false,
|
|
});
|
|
});
|
|
|
|
it('marks legacy-only sqlite registrations as migration-required', () => {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
|
|
tempRoots.push(tempRoot);
|
|
const dbPath = path.join(tempRoot, 'messages.db');
|
|
const db = new Database(dbPath);
|
|
db.exec(`
|
|
CREATE TABLE registered_groups (
|
|
jid TEXT PRIMARY KEY,
|
|
agent_type TEXT
|
|
)
|
|
`);
|
|
db.exec(`
|
|
INSERT INTO registered_groups (jid, agent_type) VALUES
|
|
('legacy-room', 'claude-code')
|
|
`);
|
|
db.close();
|
|
|
|
expect(
|
|
detectRoomRegistrationState({ dbPath, projectRoot: tempRoot }),
|
|
).toEqual({
|
|
assignedRooms: 0,
|
|
roomsByOwnerAgent: {},
|
|
legacyRegisteredGroupRows: 1,
|
|
legacyRoomMigrationRequired: true,
|
|
unexpectedDataStateFiles: [],
|
|
unexpectedDataStateDetected: false,
|
|
});
|
|
});
|
|
|
|
it('ignores legacy projection rows when matching canonical room_settings already exist', () => {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
|
|
tempRoots.push(tempRoot);
|
|
const dbPath = path.join(tempRoot, 'messages.db');
|
|
const db = new Database(dbPath);
|
|
db.exec(`
|
|
CREATE TABLE room_settings (
|
|
chat_jid TEXT PRIMARY KEY,
|
|
room_mode TEXT NOT NULL,
|
|
mode_source TEXT NOT NULL DEFAULT 'explicit',
|
|
name TEXT,
|
|
folder TEXT,
|
|
trigger_pattern TEXT,
|
|
requires_trigger INTEGER DEFAULT 1,
|
|
is_main INTEGER DEFAULT 0,
|
|
owner_agent_type TEXT,
|
|
work_dir TEXT,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE room_role_overrides (
|
|
chat_jid TEXT NOT NULL,
|
|
role TEXT NOT NULL,
|
|
agent_type TEXT NOT NULL,
|
|
agent_config_json TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
PRIMARY KEY (chat_jid, role)
|
|
);
|
|
CREATE TABLE registered_groups (
|
|
jid TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
folder TEXT NOT NULL,
|
|
trigger_pattern TEXT NOT NULL,
|
|
added_at TEXT NOT NULL,
|
|
agent_config TEXT,
|
|
requires_trigger INTEGER DEFAULT 1,
|
|
is_main INTEGER DEFAULT 0,
|
|
agent_type TEXT NOT NULL,
|
|
work_dir TEXT,
|
|
PRIMARY KEY (jid, agent_type)
|
|
)
|
|
`);
|
|
db.exec(`
|
|
INSERT INTO room_settings (
|
|
chat_jid,
|
|
room_mode,
|
|
mode_source,
|
|
name,
|
|
folder,
|
|
trigger_pattern,
|
|
requires_trigger,
|
|
is_main,
|
|
owner_agent_type,
|
|
work_dir,
|
|
updated_at
|
|
) VALUES (
|
|
'same-room',
|
|
'single',
|
|
'explicit',
|
|
'Same Room',
|
|
'same-room',
|
|
'@Andy',
|
|
1,
|
|
0,
|
|
'codex',
|
|
NULL,
|
|
'2024-01-01T00:00:00.000Z'
|
|
)
|
|
`);
|
|
db.exec(`
|
|
INSERT INTO registered_groups (
|
|
jid,
|
|
name,
|
|
folder,
|
|
trigger_pattern,
|
|
added_at,
|
|
agent_config,
|
|
requires_trigger,
|
|
is_main,
|
|
agent_type,
|
|
work_dir
|
|
) VALUES (
|
|
'same-room',
|
|
'Same Room',
|
|
'same-room',
|
|
'@Andy',
|
|
'2024-01-01T00:00:00.000Z',
|
|
NULL,
|
|
1,
|
|
0,
|
|
'codex',
|
|
NULL
|
|
)
|
|
`);
|
|
db.exec(`
|
|
INSERT INTO room_role_overrides (
|
|
chat_jid,
|
|
role,
|
|
agent_type,
|
|
agent_config_json,
|
|
created_at,
|
|
updated_at
|
|
) VALUES (
|
|
'same-room',
|
|
'owner',
|
|
'codex',
|
|
NULL,
|
|
'2024-01-01T00:00:00.000Z',
|
|
'2024-01-01T00:00:00.000Z'
|
|
)
|
|
`);
|
|
db.close();
|
|
|
|
expect(
|
|
detectRoomRegistrationState({ dbPath, projectRoot: tempRoot }),
|
|
).toEqual({
|
|
assignedRooms: 1,
|
|
roomsByOwnerAgent: {
|
|
codex: 1,
|
|
},
|
|
legacyRegisteredGroupRows: 0,
|
|
legacyRoomMigrationRequired: false,
|
|
unexpectedDataStateFiles: [],
|
|
unexpectedDataStateDetected: false,
|
|
});
|
|
});
|
|
|
|
it('marks unexpected data-state files as blockers', () => {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
|
|
tempRoots.push(tempRoot);
|
|
fs.mkdirSync(path.join(tempRoot, 'data'), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(tempRoot, 'data', 'registered_groups.json'),
|
|
'{"dc:legacy":{"name":"Legacy","folder":"legacy","trigger":"@Andy","added_at":"2024-01-01T00:00:00.000Z"}}',
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(tempRoot, 'data', 'router_state.json'),
|
|
JSON.stringify({ last_timestamp: '1234' }),
|
|
);
|
|
|
|
expect(detectRoomRegistrationState({ projectRoot: tempRoot })).toEqual({
|
|
assignedRooms: 0,
|
|
roomsByOwnerAgent: {},
|
|
legacyRegisteredGroupRows: 0,
|
|
legacyRoomMigrationRequired: false,
|
|
unexpectedDataStateFiles: ['registered_groups.json', 'router_state.json'],
|
|
unexpectedDataStateDetected: true,
|
|
});
|
|
});
|
|
|
|
it('returns no gate failure when room-registration state is clean', () => {
|
|
expect(
|
|
getRoomRegistrationGateFailure(
|
|
{
|
|
legacyRoomMigrationRequired: false,
|
|
unexpectedDataStateDetected: false,
|
|
},
|
|
'setup',
|
|
),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it('returns a targeted gate failure when legacy room migration is pending', () => {
|
|
expect(
|
|
getRoomRegistrationGateFailure(
|
|
{
|
|
legacyRoomMigrationRequired: true,
|
|
unexpectedDataStateDetected: false,
|
|
},
|
|
'verification',
|
|
),
|
|
).toEqual({
|
|
error: 'legacy_room_migration_required',
|
|
nextStep:
|
|
'Run `bun setup/index.ts --step migrate-room-registrations` before continuing with verification',
|
|
});
|
|
});
|
|
|
|
it('returns a combined gate failure when legacy migration and unexpected data files are both present', () => {
|
|
expect(
|
|
getRoomRegistrationGateFailure(
|
|
{
|
|
legacyRoomMigrationRequired: true,
|
|
unexpectedDataStateDetected: true,
|
|
},
|
|
'setup',
|
|
),
|
|
).toEqual({
|
|
error: 'legacy_migration_and_unexpected_data_state_detected',
|
|
nextStep:
|
|
'Run `bun setup/index.ts --step migrate-room-registrations` and remove or archive unexpected data state files before continuing with setup',
|
|
});
|
|
});
|
|
});
|