mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-15 18:15:40 +00:00
feat(auth): add shared resource controls
This commit is contained in:
1 parent
2d382d5474
commit
64e1d1f815
32 files changed
+1068
-43
No files matched your search
@@ -0,0 +1,180 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import ProfileRegistry from '../../src/auth/profile-registry';
|
||||
import InstanceManager from '../../src/management/instance-manager';
|
||||
import { handleResources } from '../../src/auth/commands/resources-command';
|
||||
import { handleCreate } from '../../src/auth/commands/create-command';
|
||||
|
||||
describe('auth resources command', () => {
|
||||
let tempRoot = '';
|
||||
let originalCcsHome: string | undefined;
|
||||
let originalUnifiedMode: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-auth-resources-'));
|
||||
originalCcsHome = process.env.CCS_HOME;
|
||||
originalUnifiedMode = process.env.CCS_UNIFIED_CONFIG;
|
||||
|
||||
process.env.CCS_HOME = tempRoot;
|
||||
process.env.CCS_UNIFIED_CONFIG = '1';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalCcsHome !== undefined) process.env.CCS_HOME = originalCcsHome;
|
||||
else delete process.env.CCS_HOME;
|
||||
|
||||
if (originalUnifiedMode !== undefined) process.env.CCS_UNIFIED_CONFIG = originalUnifiedMode;
|
||||
else delete process.env.CCS_UNIFIED_CONFIG;
|
||||
|
||||
if (tempRoot && fs.existsSync(tempRoot)) {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('updates an existing account shared resource mode without changing context metadata', async () => {
|
||||
const ccsDir = path.join(tempRoot, '.ccs');
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(ccsDir, 'config.yaml'),
|
||||
[
|
||||
'version: 8',
|
||||
'accounts:',
|
||||
' work:',
|
||||
' created: "2026-02-01T00:00:00.000Z"',
|
||||
' last_used: null',
|
||||
' context_mode: shared',
|
||||
' context_group: sprint-a',
|
||||
'profiles: {}',
|
||||
'cliproxy:',
|
||||
' oauth_accounts: {}',
|
||||
' providers: {}',
|
||||
' variants: {}',
|
||||
].join('\n'),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const registry = new ProfileRegistry();
|
||||
const instanceMgr = new InstanceManager();
|
||||
const ensureSpy = spyOn(InstanceManager.prototype, 'ensureInstance').mockResolvedValue(
|
||||
path.join(ccsDir, 'instances', 'work')
|
||||
);
|
||||
const lines: string[] = [];
|
||||
const originalLog = console.log;
|
||||
console.log = (...args: unknown[]) => {
|
||||
lines.push(args.map(String).join(' '));
|
||||
};
|
||||
|
||||
try {
|
||||
await handleResources(
|
||||
{
|
||||
registry,
|
||||
instanceMgr,
|
||||
version: 'test',
|
||||
},
|
||||
['work', '--mode', 'profile-local', '--json']
|
||||
);
|
||||
} finally {
|
||||
console.log = originalLog;
|
||||
ensureSpy.mockRestore();
|
||||
}
|
||||
|
||||
const output = JSON.parse(lines.join('\n')) as {
|
||||
shared_resource_mode: string;
|
||||
bare?: boolean;
|
||||
};
|
||||
expect(output.shared_resource_mode).toBe('profile-local');
|
||||
expect(output.bare).toBe(true);
|
||||
|
||||
const account = registry.getAllAccountsUnified().work;
|
||||
expect(account.shared_resource_mode).toBe('profile-local');
|
||||
expect(account.bare).toBe(true);
|
||||
expect(account.context_mode).toBe('shared');
|
||||
expect(account.context_group).toBe('sprint-a');
|
||||
});
|
||||
|
||||
it('rolls back inferred shared metadata when instance reconciliation fails', async () => {
|
||||
const ccsDir = path.join(tempRoot, '.ccs');
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(ccsDir, 'config.yaml'),
|
||||
[
|
||||
'version: 8',
|
||||
'accounts:',
|
||||
' work:',
|
||||
' created: "2026-02-01T00:00:00.000Z"',
|
||||
' last_used: null',
|
||||
'profiles: {}',
|
||||
'cliproxy:',
|
||||
' oauth_accounts: {}',
|
||||
' providers: {}',
|
||||
' variants: {}',
|
||||
].join('\n'),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const registry = new ProfileRegistry();
|
||||
const instanceMgr = new InstanceManager();
|
||||
const ensureSpy = spyOn(InstanceManager.prototype, 'ensureInstance').mockRejectedValue(
|
||||
new Error('reconcile failed')
|
||||
);
|
||||
|
||||
try {
|
||||
await expect(
|
||||
handleResources(
|
||||
{
|
||||
registry,
|
||||
instanceMgr,
|
||||
version: 'test',
|
||||
},
|
||||
['work', '--mode', 'profile-local']
|
||||
)
|
||||
).rejects.toThrow('reconcile failed');
|
||||
} finally {
|
||||
ensureSpy.mockRestore();
|
||||
}
|
||||
|
||||
const account = registry.getAllAccountsUnified().work;
|
||||
expect(account.shared_resource_mode).toBeUndefined();
|
||||
expect(account.bare).toBeUndefined();
|
||||
});
|
||||
|
||||
it('rejects --mode on auth create instead of silently ignoring it', async () => {
|
||||
const registry = new ProfileRegistry();
|
||||
const instanceMgr = new InstanceManager();
|
||||
const originalExit = process.exit;
|
||||
const originalLog = console.log;
|
||||
const originalError = console.error;
|
||||
const lines: string[] = [];
|
||||
|
||||
process.exit = ((code?: number) => {
|
||||
throw new Error(`process.exit(${code ?? 0})`);
|
||||
}) as typeof process.exit;
|
||||
console.log = (...args: unknown[]) => {
|
||||
lines.push(args.map(String).join(' '));
|
||||
};
|
||||
console.error = (...args: unknown[]) => {
|
||||
lines.push(args.map(String).join(' '));
|
||||
};
|
||||
|
||||
try {
|
||||
await expect(
|
||||
handleCreate(
|
||||
{
|
||||
registry,
|
||||
instanceMgr,
|
||||
version: 'test',
|
||||
},
|
||||
['work', '--mode', 'profile-local']
|
||||
)
|
||||
).rejects.toThrow('process.exit(7)');
|
||||
} finally {
|
||||
process.exit = originalExit;
|
||||
console.log = originalLog;
|
||||
console.error = originalError;
|
||||
}
|
||||
|
||||
expect(lines.join('\n')).toContain('Unknown option(s): "--mode"');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user