Files
Claude-Code-Workflow/ccw/tests/cli-session-share.test.js
catlog22 d0cdee2e68 feat: add CLI session sharing functionality
- Implemented share token creation and revocation for CLI sessions.
- Added a new page for viewing shared CLI sessions with SSE support.
- Introduced hooks for fetching and managing CLI session shares.
- Enhanced the IssueTerminalTab component to handle share tokens and display active shares.
- Updated API routes to support fetching and revoking share tokens.
- Added unit tests for the CLI session share manager and rate limiter.
- Updated localization files to include new strings for sharing functionality.
2026-02-09 22:57:05 +08:00

139 lines
3.9 KiB
JavaScript

/**
* Unit tests for CLI session share tokens
*/
import { afterEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
const shareUrl = new URL('../dist/core/services/cli-session-share.js', import.meta.url).href;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mod;
const originalNow = Date.now;
afterEach(() => {
Date.now = originalNow;
});
describe('CliSessionShareManager', async () => {
mod = await import(shareUrl);
it('creates a token record with expected fields', () => {
const mgr = new mod.CliSessionShareManager();
const record = mgr.createToken({
sessionKey: 's-1',
projectRoot: 'D:\\\\Claude_dms3',
mode: 'read',
ttlMs: 10_000,
});
assert.equal(record.sessionKey, 's-1');
assert.equal(record.projectRoot, 'D:\\\\Claude_dms3');
assert.equal(record.mode, 'read');
assert.match(record.token, /^[A-Za-z0-9_-]+$/);
assert.ok(record.expiresAt.endsWith('Z'));
});
it('validates only when sessionKey matches', () => {
const mgr = new mod.CliSessionShareManager();
const record = mgr.createToken({
sessionKey: 's-abc',
projectRoot: '/tmp/project',
mode: 'read',
ttlMs: 60_000,
});
assert.ok(mgr.validateToken(record.token, 's-abc'));
assert.equal(mgr.validateToken(record.token, 's-wrong'), null);
});
it('revokes tokens', () => {
const mgr = new mod.CliSessionShareManager();
const record = mgr.createToken({
sessionKey: 's-1',
projectRoot: '/tmp/project',
mode: 'write',
ttlMs: 60_000,
});
assert.equal(mgr.revokeToken(record.token), true);
assert.equal(mgr.revokeToken(record.token), false);
assert.equal(mgr.validateToken(record.token, record.sessionKey), null);
});
it('expires tokens and cleans up expired entries', () => {
let now = 1_700_000_000_000;
Date.now = () => now;
const mgr = new mod.CliSessionShareManager();
const recordA = mgr.createToken({
sessionKey: 's-a',
projectRoot: '/tmp/project',
mode: 'read',
ttlMs: 1_000,
});
const recordB = mgr.createToken({
sessionKey: 's-b',
projectRoot: '/tmp/project',
mode: 'read',
ttlMs: 10_000,
});
now += 1_001;
assert.equal(mgr.validateToken(recordA.token, recordA.sessionKey), null);
assert.ok(mgr.validateToken(recordB.token, recordB.sessionKey));
assert.equal(mgr.cleanupExpired(), 0);
now += 10_000;
assert.equal(mgr.cleanupExpired(), 1);
assert.equal(mgr.validateToken(recordB.token, recordB.sessionKey), null);
});
it('clamps ttlMs to a minimum of 1000ms', () => {
const fixedNow = 1_700_000_000_000;
Date.now = () => fixedNow;
const mgr = new mod.CliSessionShareManager();
const record = mgr.createToken({
sessionKey: 's-1',
projectRoot: '/tmp/project',
mode: 'read',
ttlMs: 1,
});
assert.equal(record.expiresAt, new Date(fixedNow + 1_000).toISOString());
});
it('lists tokens for a session (scoped to projectRoot) and sorts by expiresAt', () => {
let now = 1_700_000_000_000;
Date.now = () => now;
const mgr = new mod.CliSessionShareManager();
const t1 = mgr.createToken({
sessionKey: 's-1',
projectRoot: '/p1',
mode: 'read',
ttlMs: 5_000,
});
const t2 = mgr.createToken({
sessionKey: 's-1',
projectRoot: '/p1',
mode: 'write',
ttlMs: 10_000,
});
mgr.createToken({ sessionKey: 's-1', projectRoot: '/p2', mode: 'read', ttlMs: 10_000 });
mgr.createToken({ sessionKey: 's-2', projectRoot: '/p1', mode: 'read', ttlMs: 10_000 });
const list1 = mgr.listTokensForSession('s-1', '/p1');
assert.equal(list1.length, 2);
assert.equal(list1[0].token, t1.token);
assert.equal(list1[1].token, t2.token);
now += 5_001;
const list2 = mgr.listTokensForSession('s-1', '/p1');
assert.deepEqual(list2.map(r => r.token), [t2.token]);
});
});