feat: initial public release (MAESTRO)
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
SessionManager,
|
||||
selectOldestTaskSessionId,
|
||||
findIdleTaskSessions,
|
||||
CAPTCHA_POOL_SESSION_ID,
|
||||
type BrowserSession,
|
||||
} from './browser-session.js';
|
||||
|
||||
function fakeSession(partial: Partial<BrowserSession> & Pick<BrowserSession, 'id' | 'kind' | 'lastActiveAt'>): BrowserSession {
|
||||
return {
|
||||
id: partial.id,
|
||||
kind: partial.kind,
|
||||
taskId: partial.taskId,
|
||||
userId: partial.userId,
|
||||
browser: null,
|
||||
context: null,
|
||||
vncPort: 0,
|
||||
novncPort: 0,
|
||||
userDataDir: '',
|
||||
state: 'ready',
|
||||
xvfbProcess: null,
|
||||
x11vncProcess: null,
|
||||
websockifyProcess: null,
|
||||
display: ':99',
|
||||
createdAt: partial.createdAt ?? partial.lastActiveAt,
|
||||
lastActiveAt: partial.lastActiveAt,
|
||||
lockedByJobId: partial.lockedByJobId ?? null,
|
||||
captchaPending: partial.captchaPending,
|
||||
};
|
||||
}
|
||||
|
||||
describe('SessionManager', () => {
|
||||
it('should report availability based on system deps', () => {
|
||||
const result = SessionManager.isAvailable();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectOldestTaskSessionId', () => {
|
||||
it('returns null when no task sessions exist', () => {
|
||||
const sessions = [
|
||||
fakeSession({ id: CAPTCHA_POOL_SESSION_ID, kind: 'pool', lastActiveAt: new Date(0) }),
|
||||
];
|
||||
expect(selectOldestTaskSessionId(sessions)).toBeNull();
|
||||
});
|
||||
|
||||
it('picks the task session with the smallest lastActiveAt', () => {
|
||||
const t0 = new Date('2026-01-01T00:00:00Z');
|
||||
const t1 = new Date('2026-01-01T01:00:00Z');
|
||||
const t2 = new Date('2026-01-01T02:00:00Z');
|
||||
const sessions = [
|
||||
fakeSession({ id: 'pool', kind: 'pool', lastActiveAt: t0 }), // pool excluded
|
||||
fakeSession({ id: 'newer', kind: 'task', taskId: 'a', lastActiveAt: t2 }),
|
||||
fakeSession({ id: 'oldest', kind: 'task', taskId: 'b', lastActiveAt: t0 }),
|
||||
fakeSession({ id: 'middle', kind: 'task', taskId: 'c', lastActiveAt: t1 }),
|
||||
];
|
||||
expect(selectOldestTaskSessionId(sessions)).toBe('oldest');
|
||||
});
|
||||
|
||||
it('skips locked task sessions', () => {
|
||||
const t0 = new Date('2026-01-01T00:00:00Z');
|
||||
const t1 = new Date('2026-01-01T01:00:00Z');
|
||||
const sessions = [
|
||||
fakeSession({ id: 'oldest-locked', kind: 'task', taskId: 'a', lastActiveAt: t0, lockedByJobId: 'job-1' }),
|
||||
fakeSession({ id: 'newer-unlocked', kind: 'task', taskId: 'b', lastActiveAt: t1 }),
|
||||
];
|
||||
expect(selectOldestTaskSessionId(sessions)).toBe('newer-unlocked');
|
||||
});
|
||||
|
||||
it('returns null when all task sessions are locked', () => {
|
||||
const sessions = [
|
||||
fakeSession({ id: 'a', kind: 'task', taskId: 't1', lastActiveAt: new Date(0), lockedByJobId: 'j1' }),
|
||||
fakeSession({ id: 'b', kind: 'task', taskId: 't2', lastActiveAt: new Date(1000), lockedByJobId: 'j2' }),
|
||||
];
|
||||
expect(selectOldestTaskSessionId(sessions)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findIdleTaskSessions', () => {
|
||||
const now = new Date('2026-01-01T01:00:00Z');
|
||||
|
||||
it('returns sessions older than ttl', () => {
|
||||
const sessions = [
|
||||
fakeSession({ id: 'old', kind: 'task', taskId: 'a', lastActiveAt: new Date('2026-01-01T00:50:00Z') }), // 10 min ago
|
||||
fakeSession({ id: 'fresh', kind: 'task', taskId: 'b', lastActiveAt: new Date('2026-01-01T00:59:00Z') }), // 1 min ago
|
||||
];
|
||||
expect(findIdleTaskSessions(sessions, 300, now)).toEqual(['old']); // ttl=5min
|
||||
});
|
||||
|
||||
it('excludes pool sessions even when idle', () => {
|
||||
const sessions = [
|
||||
fakeSession({ id: CAPTCHA_POOL_SESSION_ID, kind: 'pool', lastActiveAt: new Date('2026-01-01T00:00:00Z') }),
|
||||
];
|
||||
expect(findIdleTaskSessions(sessions, 300, now)).toEqual([]);
|
||||
});
|
||||
|
||||
it('excludes locked task sessions even when idle', () => {
|
||||
const sessions = [
|
||||
fakeSession({
|
||||
id: 'locked-old', kind: 'task', taskId: 'a',
|
||||
lastActiveAt: new Date('2026-01-01T00:00:00Z'),
|
||||
lockedByJobId: 'job-1',
|
||||
}),
|
||||
];
|
||||
expect(findIdleTaskSessions(sessions, 300, now)).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty when all sessions are fresh', () => {
|
||||
const sessions = [
|
||||
fakeSession({ id: 'a', kind: 'task', taskId: 'a', lastActiveAt: new Date('2026-01-01T00:59:00Z') }),
|
||||
fakeSession({ id: 'b', kind: 'task', taskId: 'b', lastActiveAt: new Date('2026-01-01T00:58:30Z') }),
|
||||
];
|
||||
expect(findIdleTaskSessions(sessions, 300, now)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('storageState injection', () => {
|
||||
it('createLoginSession returns a fresh session with kind=login and bound profileId', async () => {
|
||||
if (!SessionManager.isAvailable()) return; // skip if Xvfb / x11vnc / websockify not installed
|
||||
const sm = new SessionManager({ vncBasePort: 5900, sessionDataDir: '/tmp/bs-test', maxSessions: 5 });
|
||||
try {
|
||||
const s = await sm.createLoginSession({ ownerId: 'u1', profileId: 7 });
|
||||
expect(s.kind).toBe('login');
|
||||
expect(s.profileId).toBe(7);
|
||||
} finally {
|
||||
await sm.destroyAll();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user