mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
283 lines
9.6 KiB
TypeScript
283 lines
9.6 KiB
TypeScript
/**
|
|
* Unit tests for skip-local-auth functionality when using remote proxy with auth token
|
|
*
|
|
* When --proxy-host and --proxy-auth-token are provided together, the system should
|
|
* skip local OAuth checks because the remote proxy handles authentication.
|
|
*
|
|
* Implementation uses: const remoteAuthToken = proxyConfig.authToken?.trim();
|
|
* const skipLocalAuth = useRemoteProxy && !!remoteAuthToken;
|
|
*/
|
|
import { describe, it, expect } from 'bun:test';
|
|
|
|
/**
|
|
* Helper to compute skipLocalAuth exactly as the implementation does
|
|
* Mirrors logic from cliproxy-executor.ts lines 503-505
|
|
*/
|
|
function computeSkipLocalAuth(
|
|
useRemoteProxy: boolean,
|
|
proxyConfig: { authToken?: string | null }
|
|
): boolean {
|
|
const remoteAuthToken = proxyConfig.authToken?.trim();
|
|
return useRemoteProxy && !!remoteAuthToken;
|
|
}
|
|
|
|
describe('skip-local-auth logic', () => {
|
|
describe('skipLocalAuth flag determination', () => {
|
|
it('should skip local auth when both useRemoteProxy and authToken are truthy', () => {
|
|
const useRemoteProxy = true;
|
|
const proxyConfig = { authToken: 'test-token-123' };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(true);
|
|
});
|
|
|
|
it('should NOT skip local auth when useRemoteProxy is false', () => {
|
|
const useRemoteProxy = false;
|
|
const proxyConfig = { authToken: 'test-token-123' };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(false);
|
|
});
|
|
|
|
it('should NOT skip local auth when authToken is undefined', () => {
|
|
const useRemoteProxy = true;
|
|
const proxyConfig = { authToken: undefined };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(false);
|
|
});
|
|
|
|
it('should NOT skip local auth when authToken is empty string', () => {
|
|
const useRemoteProxy = true;
|
|
const proxyConfig = { authToken: '' };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(false);
|
|
});
|
|
|
|
it('should NOT skip local auth when both are falsy', () => {
|
|
const useRemoteProxy = false;
|
|
const proxyConfig = { authToken: undefined };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('authToken edge cases', () => {
|
|
it('should NOT skip local auth when authToken is whitespace-only', () => {
|
|
const useRemoteProxy = true;
|
|
const proxyConfig = { authToken: ' ' };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(false);
|
|
});
|
|
|
|
it('should NOT skip local auth when authToken is tabs and newlines', () => {
|
|
const useRemoteProxy = true;
|
|
const proxyConfig = { authToken: '\t\n\r' };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(false);
|
|
});
|
|
|
|
it('should NOT skip local auth when authToken is null', () => {
|
|
const useRemoteProxy = true;
|
|
const proxyConfig = { authToken: null };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(false);
|
|
});
|
|
|
|
it('should skip local auth when authToken has leading/trailing whitespace but valid content', () => {
|
|
const useRemoteProxy = true;
|
|
const proxyConfig = { authToken: ' valid-token-123 ' };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(true);
|
|
});
|
|
|
|
it('should skip local auth when authToken contains special characters', () => {
|
|
const useRemoteProxy = true;
|
|
const proxyConfig = { authToken: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test' };
|
|
|
|
const skipLocalAuth = computeSkipLocalAuth(useRemoteProxy, proxyConfig);
|
|
|
|
expect(skipLocalAuth).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('OAuth check bypass scenarios', () => {
|
|
it('should document that OAuth is skipped for remote proxy with auth', () => {
|
|
// This test documents the expected behavior:
|
|
// When using remote proxy with auth token, the remote server
|
|
// already has its own OAuth sessions, so local OAuth is unnecessary
|
|
const scenario = {
|
|
useRemoteProxy: true,
|
|
authToken: 'bearer-token',
|
|
providerRequiresOAuth: true,
|
|
};
|
|
|
|
const skipLocalAuth = scenario.useRemoteProxy && scenario.authToken;
|
|
const shouldTriggerLocalOAuth = scenario.providerRequiresOAuth && !skipLocalAuth;
|
|
|
|
expect(skipLocalAuth).toBeTruthy();
|
|
expect(shouldTriggerLocalOAuth).toBe(false);
|
|
});
|
|
|
|
it('should document that OAuth runs when no remote proxy', () => {
|
|
const scenario = {
|
|
useRemoteProxy: false,
|
|
authToken: undefined,
|
|
providerRequiresOAuth: true,
|
|
};
|
|
|
|
const skipLocalAuth = scenario.useRemoteProxy && scenario.authToken;
|
|
const shouldTriggerLocalOAuth = scenario.providerRequiresOAuth && !skipLocalAuth;
|
|
|
|
expect(skipLocalAuth).toBeFalsy();
|
|
expect(shouldTriggerLocalOAuth).toBe(true);
|
|
});
|
|
|
|
it('should document that OAuth runs when remote proxy has no auth token', () => {
|
|
// Edge case: remote proxy configured but no auth token
|
|
// This should fall back to local OAuth
|
|
const scenario = {
|
|
useRemoteProxy: true,
|
|
authToken: undefined,
|
|
providerRequiresOAuth: true,
|
|
};
|
|
|
|
const skipLocalAuth = scenario.useRemoteProxy && scenario.authToken;
|
|
const shouldTriggerLocalOAuth = scenario.providerRequiresOAuth && !skipLocalAuth;
|
|
|
|
expect(skipLocalAuth).toBeFalsy();
|
|
expect(shouldTriggerLocalOAuth).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('preflight quota check bypass', () => {
|
|
it('should skip preflight for agy provider when using remote proxy with auth', () => {
|
|
const provider = 'agy';
|
|
const skipLocalAuth = true;
|
|
|
|
const shouldRunPreflight = provider === 'agy' && !skipLocalAuth;
|
|
|
|
expect(shouldRunPreflight).toBe(false);
|
|
});
|
|
|
|
it('should run preflight for agy provider when using local mode', () => {
|
|
const provider = 'agy';
|
|
const skipLocalAuth = false;
|
|
|
|
const shouldRunPreflight = provider === 'agy' && !skipLocalAuth;
|
|
|
|
expect(shouldRunPreflight).toBe(true);
|
|
});
|
|
|
|
it('should not run preflight for non-agy providers regardless of mode', () => {
|
|
const providers = ['gemini', 'codex', 'ghcp', 'kiro'];
|
|
|
|
for (const provider of providers) {
|
|
const shouldRunPreflight = provider === 'agy' && !false;
|
|
expect(shouldRunPreflight).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('model configuration bypass', () => {
|
|
it('should skip model config when using remote proxy with auth', () => {
|
|
const supportsModelConfig = true;
|
|
const skipLocalAuth = true;
|
|
|
|
const shouldConfigureModel = supportsModelConfig && !skipLocalAuth;
|
|
|
|
expect(shouldConfigureModel).toBe(false);
|
|
});
|
|
|
|
it('should run model config when using local mode', () => {
|
|
const supportsModelConfig = true;
|
|
const skipLocalAuth = false;
|
|
|
|
const shouldConfigureModel = supportsModelConfig && !skipLocalAuth;
|
|
|
|
expect(shouldConfigureModel).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('broken model warning behavior', () => {
|
|
it('should show broken model warning in BOTH remote and local modes', () => {
|
|
// Updated behavior: warnings always shown (with different messaging for remote)
|
|
// Remote users need to know about broken models too
|
|
const currentModel = 'some-broken-model';
|
|
const isModelBroken = true;
|
|
|
|
// Warning should show regardless of skipLocalAuth
|
|
const shouldWarnRemote = currentModel && isModelBroken; // skipLocalAuth=true
|
|
const shouldWarnLocal = currentModel && isModelBroken; // skipLocalAuth=false
|
|
|
|
expect(shouldWarnRemote).toBe(true);
|
|
expect(shouldWarnLocal).toBe(true);
|
|
});
|
|
|
|
it('should show different message for remote vs local mode', () => {
|
|
const skipLocalAuth = true;
|
|
const currentModel = 'some-broken-model';
|
|
const isModelBroken = true;
|
|
|
|
// When remote: "Note: Model may be overridden by remote proxy configuration."
|
|
// When local: "Run ccs <provider> --config to change model."
|
|
const remoteMessage = skipLocalAuth
|
|
? 'Note: Model may be overridden by remote proxy configuration.'
|
|
: 'Run "ccs provider --config" to change model.';
|
|
|
|
expect(remoteMessage).toContain('remote proxy');
|
|
});
|
|
});
|
|
|
|
describe('CI/CD workflow scenarios', () => {
|
|
it('should enable headless CI workflow with remote proxy', () => {
|
|
// Simulate GitHub Actions workflow configuration
|
|
const workflowConfig = {
|
|
headless: true,
|
|
proxyHost: 'proxy.example.com',
|
|
proxyPort: 443,
|
|
proxyProtocol: 'https',
|
|
proxyAuthToken: 'github-secret-token',
|
|
remoteOnly: true,
|
|
};
|
|
|
|
// Determine if this configuration should skip local OAuth
|
|
const useRemoteProxy = !!workflowConfig.proxyHost;
|
|
const skipLocalAuth = useRemoteProxy && !!workflowConfig.proxyAuthToken;
|
|
|
|
expect(useRemoteProxy).toBe(true);
|
|
expect(skipLocalAuth).toBe(true);
|
|
});
|
|
|
|
it('should require local OAuth when no proxy configured', () => {
|
|
// Simulate local development without proxy
|
|
const localConfig = {
|
|
headless: false,
|
|
proxyHost: undefined,
|
|
proxyAuthToken: undefined,
|
|
};
|
|
|
|
const useRemoteProxy = !!localConfig.proxyHost;
|
|
const skipLocalAuth = useRemoteProxy && !!localConfig.proxyAuthToken;
|
|
|
|
expect(useRemoteProxy).toBe(false);
|
|
expect(skipLocalAuth).toBe(false);
|
|
});
|
|
});
|
|
});
|