130 lines
4.3 KiB
TypeScript
130 lines
4.3 KiB
TypeScript
import * as fs from 'fs/promises';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
|
|
import {
|
|
AGENT_TEAMS_IDENTITY_STORE_PATH_ENV,
|
|
applyAgentTeamsIdentityEnv,
|
|
ensureAgentTeamsClientIdentity,
|
|
getAgentTeamsIdentityStorePath,
|
|
getSentryAnonymousUserId,
|
|
readAgentTeamsIdentityStore,
|
|
} from '@main/services/identity/AgentTeamsIdentityStore';
|
|
import { setAppDataBasePath, setClaudeBasePathOverride } from '@main/utils/pathDecoder';
|
|
|
|
const LEGACY_CLIENT_ID = '22222222-2222-4222-8222-222222222222';
|
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
|
|
describe('AgentTeamsIdentityStore', () => {
|
|
let tempRoot: string;
|
|
let tempHome: string;
|
|
let tempAppDataBase: string;
|
|
let previousHome: string | undefined;
|
|
|
|
beforeEach(async () => {
|
|
tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'agent-teams-identity-'));
|
|
tempHome = path.join(tempRoot, 'home');
|
|
tempAppDataBase = path.join(tempRoot, 'app-user-data');
|
|
await fs.mkdir(tempHome, { recursive: true });
|
|
previousHome = process.env.HOME;
|
|
process.env.HOME = tempHome;
|
|
setClaudeBasePathOverride(null);
|
|
setAppDataBasePath(tempAppDataBase);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
setClaudeBasePathOverride(null);
|
|
setAppDataBasePath(null);
|
|
if (previousHome === undefined) {
|
|
delete process.env.HOME;
|
|
} else {
|
|
process.env.HOME = previousHome;
|
|
}
|
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
it('creates and reuses a stable app-data UUID', async () => {
|
|
const first = await ensureAgentTeamsClientIdentity();
|
|
const second = await ensureAgentTeamsClientIdentity();
|
|
const persisted = await readAgentTeamsIdentityStore();
|
|
|
|
expect(first.clientId).toMatch(UUID_PATTERN);
|
|
expect(second.clientId).toBe(first.clientId);
|
|
expect(first.source).toBe('created');
|
|
expect(second.source).toBe('app-data');
|
|
expect(persisted?.schemaVersion).toBe(1);
|
|
expect(persisted?.clientId).toBe(first.clientId);
|
|
});
|
|
|
|
it('falls back safely when app-data JSON or UUID is invalid', async () => {
|
|
const storePath = getAgentTeamsIdentityStorePath();
|
|
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
|
await fs.writeFile(storePath, '{not-json', 'utf8');
|
|
|
|
const fromInvalidJson = await ensureAgentTeamsClientIdentity();
|
|
expect(fromInvalidJson.clientId).toMatch(UUID_PATTERN);
|
|
|
|
await fs.writeFile(
|
|
storePath,
|
|
JSON.stringify({
|
|
schemaVersion: 1,
|
|
clientId: 'not-a-uuid',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}),
|
|
'utf8'
|
|
);
|
|
|
|
const fromInvalidUuid = await ensureAgentTeamsClientIdentity();
|
|
expect(fromInvalidUuid.clientId).toMatch(UUID_PATTERN);
|
|
expect(fromInvalidUuid.clientId).not.toBe('not-a-uuid');
|
|
});
|
|
|
|
it('soft-migrates legacy ~/.claude.json agentTeams into app data', async () => {
|
|
await fs.writeFile(
|
|
path.join(tempHome, '.claude.json'),
|
|
JSON.stringify({
|
|
agentTeams: {
|
|
clientId: LEGACY_CLIENT_ID,
|
|
session: {
|
|
accessToken: 'legacy-access',
|
|
refreshToken: 'legacy-refresh',
|
|
},
|
|
capabilities: {
|
|
token: 'legacy-capabilities',
|
|
},
|
|
},
|
|
}),
|
|
'utf8'
|
|
);
|
|
|
|
const identity = await ensureAgentTeamsClientIdentity();
|
|
const persisted = await readAgentTeamsIdentityStore();
|
|
const legacy = JSON.parse(await fs.readFile(path.join(tempHome, '.claude.json'), 'utf8')) as {
|
|
agentTeams?: { clientId?: string };
|
|
};
|
|
|
|
expect(identity).toMatchObject({
|
|
clientId: LEGACY_CLIENT_ID,
|
|
source: 'legacy-global-config',
|
|
});
|
|
expect(persisted?.clientId).toBe(LEGACY_CLIENT_ID);
|
|
expect(legacy.agentTeams?.clientId).toBe(LEGACY_CLIENT_ID);
|
|
});
|
|
|
|
it('builds deterministic Sentry-safe anonymous user ids', () => {
|
|
const hashed = getSentryAnonymousUserId(LEGACY_CLIENT_ID);
|
|
|
|
expect(hashed).toBe(getSentryAnonymousUserId(LEGACY_CLIENT_ID));
|
|
expect(hashed).not.toBe(LEGACY_CLIENT_ID);
|
|
expect(hashed).toMatch(/^[a-f0-9]{64}$/);
|
|
});
|
|
|
|
it('sets the orchestrator identity store env path', () => {
|
|
const env: NodeJS.ProcessEnv = {};
|
|
|
|
applyAgentTeamsIdentityEnv(env);
|
|
|
|
expect(env[AGENT_TEAMS_IDENTITY_STORE_PATH_ENV]).toBe(getAgentTeamsIdentityStorePath());
|
|
});
|
|
});
|