156 lines
5.1 KiB
TypeScript
156 lines
5.1 KiB
TypeScript
|
|
import { describe, it, expect } from 'vitest';
|
||
|
|
import { SessionGate } from '../src/gate/session-gate.js';
|
||
|
|
import type { TagMatchResult, PromptIndexEntry } from '../src/gate/tag-matcher.js';
|
||
|
|
|
||
|
|
function makeMatchResult(names: string[]): TagMatchResult {
|
||
|
|
return {
|
||
|
|
fullContent: names.map((name) => ({
|
||
|
|
name,
|
||
|
|
priority: 5,
|
||
|
|
summary: null,
|
||
|
|
chapters: null,
|
||
|
|
content: `Content of ${name}`,
|
||
|
|
})),
|
||
|
|
indexOnly: [],
|
||
|
|
remaining: [],
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('SessionGate', () => {
|
||
|
|
it('creates a gated session when project is gated', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.createSession('s1', true);
|
||
|
|
expect(gate.isGated('s1')).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('creates an ungated session when project is not gated', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.createSession('s1', false);
|
||
|
|
expect(gate.isGated('s1')).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('unknown sessions are treated as ungated', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
expect(gate.isGated('nonexistent')).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('getSession returns null for unknown sessions', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
expect(gate.getSession('nonexistent')).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('getSession returns session state', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.createSession('s1', true);
|
||
|
|
const state = gate.getSession('s1');
|
||
|
|
expect(state).not.toBeNull();
|
||
|
|
expect(state!.gated).toBe(true);
|
||
|
|
expect(state!.tags).toEqual([]);
|
||
|
|
expect(state!.retrievedPrompts.size).toBe(0);
|
||
|
|
expect(state!.briefing).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('ungate marks session as ungated and records tags', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.createSession('s1', true);
|
||
|
|
|
||
|
|
gate.ungate('s1', ['zigbee', 'mqtt'], makeMatchResult(['prompt-a', 'prompt-b']));
|
||
|
|
|
||
|
|
expect(gate.isGated('s1')).toBe(false);
|
||
|
|
const state = gate.getSession('s1');
|
||
|
|
expect(state!.tags).toEqual(['zigbee', 'mqtt']);
|
||
|
|
expect(state!.retrievedPrompts.has('prompt-a')).toBe(true);
|
||
|
|
expect(state!.retrievedPrompts.has('prompt-b')).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('ungate appends tags on repeated calls', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.createSession('s1', true);
|
||
|
|
|
||
|
|
gate.ungate('s1', ['zigbee'], makeMatchResult(['p1']));
|
||
|
|
gate.ungate('s1', ['mqtt'], makeMatchResult(['p2']));
|
||
|
|
|
||
|
|
const state = gate.getSession('s1');
|
||
|
|
expect(state!.tags).toEqual(['zigbee', 'mqtt']);
|
||
|
|
expect(state!.retrievedPrompts.has('p1')).toBe(true);
|
||
|
|
expect(state!.retrievedPrompts.has('p2')).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('ungate is no-op for unknown sessions', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
// Should not throw
|
||
|
|
gate.ungate('nonexistent', ['tag'], makeMatchResult(['p']));
|
||
|
|
});
|
||
|
|
|
||
|
|
it('addRetrievedPrompts records additional prompts', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.createSession('s1', true);
|
||
|
|
gate.ungate('s1', ['zigbee'], makeMatchResult(['p1']));
|
||
|
|
|
||
|
|
gate.addRetrievedPrompts('s1', ['mqtt', 'lights'], ['p2', 'p3']);
|
||
|
|
|
||
|
|
const state = gate.getSession('s1');
|
||
|
|
expect(state!.tags).toEqual(['zigbee', 'mqtt', 'lights']);
|
||
|
|
expect(state!.retrievedPrompts.has('p2')).toBe(true);
|
||
|
|
expect(state!.retrievedPrompts.has('p3')).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('addRetrievedPrompts is no-op for unknown sessions', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.addRetrievedPrompts('nonexistent', ['tag'], ['p']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('filterAlreadySent removes already-sent prompts', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.createSession('s1', true);
|
||
|
|
gate.ungate('s1', ['zigbee'], makeMatchResult(['p1']));
|
||
|
|
|
||
|
|
const prompts: PromptIndexEntry[] = [
|
||
|
|
{ name: 'p1', priority: 5, summary: 'already sent', chapters: null, content: 'x' },
|
||
|
|
{ name: 'p2', priority: 5, summary: 'new', chapters: null, content: 'y' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const filtered = gate.filterAlreadySent('s1', prompts);
|
||
|
|
expect(filtered).toHaveLength(1);
|
||
|
|
expect(filtered[0]!.name).toBe('p2');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('filterAlreadySent returns all prompts for unknown sessions', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
const prompts: PromptIndexEntry[] = [
|
||
|
|
{ name: 'p1', priority: 5, summary: null, chapters: null, content: 'x' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const filtered = gate.filterAlreadySent('nonexistent', prompts);
|
||
|
|
expect(filtered).toHaveLength(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('removeSession cleans up state', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.createSession('s1', true);
|
||
|
|
expect(gate.getSession('s1')).not.toBeNull();
|
||
|
|
|
||
|
|
gate.removeSession('s1');
|
||
|
|
expect(gate.getSession('s1')).toBeNull();
|
||
|
|
expect(gate.isGated('s1')).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('removeSession is safe for unknown sessions', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.removeSession('nonexistent'); // Should not throw
|
||
|
|
});
|
||
|
|
|
||
|
|
it('manages multiple sessions independently', () => {
|
||
|
|
const gate = new SessionGate();
|
||
|
|
gate.createSession('s1', true);
|
||
|
|
gate.createSession('s2', false);
|
||
|
|
|
||
|
|
expect(gate.isGated('s1')).toBe(true);
|
||
|
|
expect(gate.isGated('s2')).toBe(false);
|
||
|
|
|
||
|
|
gate.ungate('s1', ['zigbee'], makeMatchResult(['p1']));
|
||
|
|
expect(gate.isGated('s1')).toBe(false);
|
||
|
|
expect(gate.getSession('s2')!.tags).toEqual([]); // s2 untouched
|
||
|
|
});
|
||
|
|
});
|