test(ai-roles): cover role-resolution, CASL gate, model override; hide disabled badge
Release-cycle test audit found the role feature's security-critical paths untested. Adds real unit tests (against the actual functions): - resolveRoleForRequest invariants: role comes from chat.roleId not body.roleId (no per-turn swap), lookup scoped to workspace.id, disabled/soft-deleted role -> null, new-chat uses body.roleId, stale chatId falls back. - CASL admin gate: non-admin create/update/delete -> Forbidden and service not called; admin delegates with workspace.id; list() is member-reachable. - roleModelOverride: unknown driver dropped (never reaches getChatModel's throwing default), valid override passes through, blanks ignored. - getChatModel override success path (cross-driver fetch + decrypt; chatModel- only reuse), and service update/remove cross-workspace 'not found' guards + modelConfig tri-state. Tiny fix: findByCreator badge left-join now also requires enabled=true, so a disabled role (downgraded to universal by resolveRoleForRequest) no longer shows a misleading chat-list badge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -84,4 +84,41 @@ describe('AiService.getChatModel role model override', () => {
|
||||
// The override driver's creds were looked up for the right driver.
|
||||
expect(aiProviderCredentialsRepo.find).toHaveBeenCalledWith('ws-1', 'gemini');
|
||||
});
|
||||
|
||||
it('cross-driver override with creds present: resolves without throwing, using the OVERRIDE driver creds', async () => {
|
||||
// Workspace driver is openai; the role overrides to gemini, which HAS creds.
|
||||
const { service, aiProviderCredentialsRepo, secretBox } = makeService({
|
||||
workspaceDriver: 'openai',
|
||||
credsApiKeyEnc: 'enc-gemini-key',
|
||||
});
|
||||
|
||||
const model = await service.getChatModel('ws-1', {
|
||||
driver: 'gemini',
|
||||
chatModel: 'gemini-2.0-flash',
|
||||
roleName: 'Researcher',
|
||||
});
|
||||
|
||||
// A real LanguageModel was built (no 503).
|
||||
expect(model).toBeDefined();
|
||||
// Creds were fetched for the OVERRIDE driver, then decrypted.
|
||||
expect(aiProviderCredentialsRepo.find).toHaveBeenCalledWith('ws-1', 'gemini');
|
||||
expect(secretBox.decryptSecret).toHaveBeenCalledWith('enc-gemini-key');
|
||||
});
|
||||
|
||||
it('chatModel-only override (no driver): reuses the workspace driver+creds, no creds lookup/decrypt', async () => {
|
||||
// No override.driver => the workspace openai driver + its apiKey are reused;
|
||||
// ai_provider_credentials must NOT be queried and nothing is decrypted.
|
||||
const { service, aiProviderCredentialsRepo, secretBox } = makeService({
|
||||
workspaceDriver: 'openai',
|
||||
});
|
||||
|
||||
const model = await service.getChatModel('ws-1', {
|
||||
chatModel: 'gpt-4o',
|
||||
roleName: 'Writer',
|
||||
});
|
||||
|
||||
expect(model).toBeDefined();
|
||||
expect(aiProviderCredentialsRepo.find).not.toHaveBeenCalled();
|
||||
expect(secretBox.decryptSecret).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user