150 lines
6.5 KiB
TypeScript
150 lines
6.5 KiB
TypeScript
import {
|
|
buildDirectTmuxRestartCommand,
|
|
buildDirectTmuxRestartEnvAssignments,
|
|
hasAnthropicCompatibleAuthTokenEnv,
|
|
isAnthropicCompatibleBaseUrl,
|
|
isInteractiveShellCommand,
|
|
shellQuote,
|
|
} from '@main/services/team/provisioning/TeamProvisioningDirectRestart';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
describe('TeamProvisioningDirectRestart', () => {
|
|
it('quotes shell values without losing apostrophes or empty strings', () => {
|
|
expect(shellQuote('')).toBe("''");
|
|
expect(shellQuote('/tmp/demo path')).toBe("'/tmp/demo path'");
|
|
expect(shellQuote("worker's path")).toBe("'worker'\\''s path'");
|
|
});
|
|
|
|
it('detects interactive shell pane commands by basename', () => {
|
|
expect(isInteractiveShellCommand('/bin/zsh')).toBe(true);
|
|
expect(isInteractiveShellCommand(' FISH ')).toBe(true);
|
|
expect(isInteractiveShellCommand('node')).toBe(false);
|
|
expect(isInteractiveShellCommand(undefined)).toBe(false);
|
|
});
|
|
|
|
it('classifies Anthropic-compatible base URLs without accepting first-party or credential URLs', () => {
|
|
expect(isAnthropicCompatibleBaseUrl('http://localhost:1234')).toBe(true);
|
|
expect(isAnthropicCompatibleBaseUrl('https://proxy.example.test')).toBe(true);
|
|
expect(isAnthropicCompatibleBaseUrl('https://api.anthropic.com')).toBe(false);
|
|
expect(isAnthropicCompatibleBaseUrl('https://api-staging.anthropic.com')).toBe(false);
|
|
expect(isAnthropicCompatibleBaseUrl('http://token@localhost:1234')).toBe(false);
|
|
expect(isAnthropicCompatibleBaseUrl('not a url')).toBe(false);
|
|
expect(isAnthropicCompatibleBaseUrl('')).toBe(false);
|
|
});
|
|
|
|
it('requires both compatible base URL and auth token for compatible auth token env', () => {
|
|
expect(
|
|
hasAnthropicCompatibleAuthTokenEnv({
|
|
ANTHROPIC_BASE_URL: 'http://localhost:1234',
|
|
ANTHROPIC_AUTH_TOKEN: 'local-token',
|
|
})
|
|
).toBe(true);
|
|
expect(
|
|
hasAnthropicCompatibleAuthTokenEnv({
|
|
ANTHROPIC_BASE_URL: 'http://localhost:1234',
|
|
ANTHROPIC_AUTH_TOKEN: ' ',
|
|
})
|
|
).toBe(false);
|
|
expect(
|
|
hasAnthropicCompatibleAuthTokenEnv({
|
|
ANTHROPIC_BASE_URL: 'https://api.anthropic.com',
|
|
ANTHROPIC_AUTH_TOKEN: 'stale-token',
|
|
})
|
|
).toBe(false);
|
|
});
|
|
|
|
it('preserves provider-specific direct restart env while resetting provider selection flags', () => {
|
|
const assignments = buildDirectTmuxRestartEnvAssignments(
|
|
{
|
|
CODEX_HOME: '/tmp/codex home',
|
|
CODEX_CLI_PATH: '/opt/codex/bin/codex',
|
|
CLAUDE_CODE_USE_GEMINI: '1',
|
|
CLAUDE_CODE_ENTRY_PROVIDER: 'gemini',
|
|
CLAUDE_CODE_CODEX_BACKEND: 'codex-native',
|
|
CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD: 'chatgpt',
|
|
},
|
|
'codex'
|
|
);
|
|
|
|
expect(assignments).toContain("CLAUDECODE='1'");
|
|
expect(assignments).toContain("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS='1'");
|
|
expect(assignments).toContain("CODEX_HOME='/tmp/codex home'");
|
|
expect(assignments).toContain("CODEX_CLI_PATH='/opt/codex/bin/codex'");
|
|
expect(assignments).toContain("CLAUDE_CODE_USE_GEMINI=''");
|
|
expect(assignments).toContain("CLAUDE_CODE_ENTRY_PROVIDER='codex'");
|
|
expect(assignments).toContain("CLAUDE_CODE_CODEX_BACKEND='codex-native'");
|
|
expect(assignments).toContain("CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD='chatgpt'");
|
|
expect(assignments).toContain("CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST='1'");
|
|
});
|
|
|
|
it('preserves Anthropic-compatible tokens but blanks stale first-party auth tokens', () => {
|
|
const compatibleAssignments = buildDirectTmuxRestartEnvAssignments(
|
|
{
|
|
ANTHROPIC_BASE_URL: ' http://localhost:1234 ',
|
|
ANTHROPIC_AUTH_TOKEN: ' local-token ',
|
|
ANTHROPIC_API_KEY: '',
|
|
},
|
|
'anthropic'
|
|
);
|
|
|
|
expect(compatibleAssignments).toContain("ANTHROPIC_BASE_URL='http://localhost:1234'");
|
|
expect(compatibleAssignments).toContain("ANTHROPIC_AUTH_TOKEN='local-token'");
|
|
expect(compatibleAssignments).toContain("ANTHROPIC_API_KEY=''");
|
|
|
|
const firstPartyAssignments = buildDirectTmuxRestartEnvAssignments(
|
|
{
|
|
ANTHROPIC_BASE_URL: 'https://api.anthropic.com',
|
|
ANTHROPIC_AUTH_TOKEN: 'stale-token',
|
|
},
|
|
'anthropic'
|
|
);
|
|
|
|
expect(firstPartyAssignments).toContain("ANTHROPIC_BASE_URL='https://api.anthropic.com'");
|
|
expect(firstPartyAssignments).toContain("ANTHROPIC_AUTH_TOKEN=''");
|
|
expect(firstPartyAssignments).not.toContain('stale-token');
|
|
});
|
|
|
|
it('blanks competing Anthropic helper auth carriers for direct restart helper mode', () => {
|
|
const assignments = buildDirectTmuxRestartEnvAssignments(
|
|
{
|
|
CLAUDE_TEAM_ANTHROPIC_AUTH_MODE: 'api_key_helper',
|
|
CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH:
|
|
'/tmp/team-runtime-auth/demo/runtime-settings-anthropic.json',
|
|
ANTHROPIC_API_KEY: 'sk-ant-direct-restart-should-not-leak',
|
|
ANTHROPIC_AUTH_TOKEN: 'direct-restart-token-should-not-leak',
|
|
CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR: '3',
|
|
CLAUDE_CODE_OAUTH_TOKEN: 'direct-restart-oauth-token-should-not-leak',
|
|
CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR: '4',
|
|
},
|
|
'anthropic'
|
|
);
|
|
|
|
expect(assignments).toContain("CLAUDE_TEAM_ANTHROPIC_AUTH_MODE='api_key_helper'");
|
|
expect(assignments).toContain(
|
|
"CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH='/tmp/team-runtime-auth/demo/runtime-settings-anthropic.json'"
|
|
);
|
|
expect(assignments).toContain("ANTHROPIC_API_KEY=''");
|
|
expect(assignments).toContain("ANTHROPIC_AUTH_TOKEN=''");
|
|
expect(assignments).toContain("CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR=''");
|
|
expect(assignments).toContain("CLAUDE_CODE_OAUTH_TOKEN=''");
|
|
expect(assignments).toContain("CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR=''");
|
|
expect(assignments).not.toContain('sk-ant-direct-restart-should-not-leak');
|
|
expect(assignments).not.toContain('direct-restart-token-should-not-leak');
|
|
expect(assignments).not.toContain('direct-restart-oauth-token-should-not-leak');
|
|
});
|
|
|
|
it('builds a restart command that preserves cwd, binary and args quoting', () => {
|
|
const command = buildDirectTmuxRestartCommand({
|
|
cwd: '/tmp/team work',
|
|
env: { CODEX_HOME: '/tmp/codex' },
|
|
providerId: 'codex',
|
|
binaryPath: '/usr/local/bin/claude',
|
|
args: ['--model', "gpt worker's model"],
|
|
});
|
|
|
|
expect(command).toContain("cd '/tmp/team work' && env");
|
|
expect(command).toContain("CODEX_HOME='/tmp/codex'");
|
|
expect(command).toContain("'/usr/local/bin/claude' '--model' 'gpt worker'\\''s model'");
|
|
expect(command).toContain('__CLAUDE_TEAMMATE_EXIT__:%s');
|
|
});
|
|
});
|