Repository navigation
test(profiles): add integration tests for ProfilesClient and switchLlm #186
Changes from all commits
dc7b49b
105c084
8d055e8
4ff2a4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { | |||||||||||
| deleteWorkspaceFile, | ||||||||||||
| readWorkspaceFile, | ||||||||||||
| sleep, | ||||||||||||
| uniqueDirName, | ||||||||||||
| uniqueFileName, | ||||||||||||
| workspaceFileExists, | ||||||||||||
| } from './test-utils'; | ||||||||||||
|
|
@@ -132,6 +133,18 @@ describe('Deterministic API Integration Tests', () => { | |||||||||||
| conversation.switchProfile('__profile_that_should_not_exist__') | ||||||||||||
| ).rejects.toBeInstanceOf(HttpError); | ||||||||||||
|
|
||||||||||||
| // switchLlm swaps the LLM in place, keyed by usage_id (first-write-wins | ||||||||||||
| // in the registry). Reusing the agent's existing usage_id would be a | ||||||||||||
| // silent no-op, so use a fresh one and read the model back off the | ||||||||||||
| // agent to prove the swap actually took effect. | ||||||||||||
| await conversation.switchLlm({ | ||||||||||||
| model: 'dummy/switched-model', | ||||||||||||
| api_key: 'dummy-key', | ||||||||||||
| usage_id: 'switched', | ||||||||||||
| }); | ||||||||||||
| const switchedAgent = await conversation.state.getAgent(); | ||||||||||||
| expect(switchedAgent.llm.model).toBe('dummy/switched-model'); | ||||||||||||
|
|
||||||||||||
| const acpConversation = await manager.acp.createConversation( | ||||||||||||
| { | ||||||||||||
| kind: 'Agent', | ||||||||||||
|
|
@@ -215,4 +228,61 @@ describe('Deterministic API Integration Tests', () => { | |||||||||||
| }, | ||||||||||||
| config.testTimeout | ||||||||||||
| ); | ||||||||||||
|
|
||||||||||||
| it( | ||||||||||||
| 'round-trips a profile through save, get, list, activate, rename, and delete', | ||||||||||||
| async () => { | ||||||||||||
| const profileName = uniqueDirName('it-profile'); | ||||||||||||
| const renamedProfile = `${profileName}-renamed`; | ||||||||||||
| const llm = { model: 'dummy/model', api_key: 'dummy-key' }; | ||||||||||||
|
|
||||||||||||
| try { | ||||||||||||
| const saved = await manager.profiles.saveProfile(profileName, { llm }); | ||||||||||||
| expect(saved.name).toBe(profileName); | ||||||||||||
|
|
||||||||||||
| const detail = await manager.profiles.getProfile(profileName); | ||||||||||||
| expect(detail.name).toBe(profileName); | ||||||||||||
| // The saved LLM config round-trips... | ||||||||||||
| expect(detail.config.model).toBe('dummy/model'); | ||||||||||||
| // ...but the default (no X-Expose-Secrets) response nulls api_key and | ||||||||||||
| // reports the presence of the saved key via api_key_set instead. | ||||||||||||
| expect(detail.config.api_key).toBeNull(); | ||||||||||||
| expect(detail.api_key_set).toBe(true); | ||||||||||||
|
|
||||||||||||
| const list = await manager.profiles.listProfiles(); | ||||||||||||
| expect(list.profiles.some((profile) => profile.name === profileName)).toBe(true); | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: This only checks that the profile name appears in the list.
Suggested change
|
||||||||||||
|
|
||||||||||||
| const activated = await manager.profiles.activateProfile(profileName); | ||||||||||||
| expect(activated.name).toBe(profileName); | ||||||||||||
| expect(activated.llm_applied).toBe(true); | ||||||||||||
|
|
||||||||||||
| const afterActivate = await manager.profiles.listProfiles(); | ||||||||||||
| expect(afterActivate.active_profile).toBe(profileName); | ||||||||||||
|
|
||||||||||||
| const renamed = await manager.profiles.renameProfile(profileName, renamedProfile); | ||||||||||||
| expect(renamed.name).toBe(renamedProfile); | ||||||||||||
|
|
||||||||||||
| const renamedDetail = await manager.profiles.getProfile(renamedProfile); | ||||||||||||
| expect(renamedDetail.name).toBe(renamedProfile); | ||||||||||||
| // The config survives the (atomic) rename... | ||||||||||||
| expect(renamedDetail.config.model).toBe('dummy/model'); | ||||||||||||
| // ...and because the renamed profile was the active one, the | ||||||||||||
| // active_profile pointer follows it to the new name. | ||||||||||||
| const afterRename = await manager.profiles.listProfiles(); | ||||||||||||
| expect(afterRename.active_profile).toBe(renamedProfile); | ||||||||||||
| // The original name stops resolving once the profile is renamed. | ||||||||||||
| await expect(manager.profiles.getProfile(profileName)).rejects.toBeInstanceOf(HttpError); | ||||||||||||
|
|
||||||||||||
| const deleted = await manager.profiles.deleteProfile(renamedProfile); | ||||||||||||
| expect(deleted.name).toBe(renamedProfile); | ||||||||||||
|
|
||||||||||||
| const finalList = await manager.profiles.listProfiles(); | ||||||||||||
| expect(finalList.profiles.some((profile) => profile.name === renamedProfile)).toBe(false); | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion:
Suggested change
|
||||||||||||
| } finally { | ||||||||||||
| await manager.profiles.deleteProfile(profileName).catch(() => undefined); | ||||||||||||
| await manager.profiles.deleteProfile(renamedProfile).catch(() => undefined); | ||||||||||||
| } | ||||||||||||
| }, | ||||||||||||
| config.testTimeout | ||||||||||||
| ); | ||||||||||||
| }); | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor:
'switched'is a static string — if the test server ever accumulates state across runs, thisusage_idwould already be registered andswitchLlmwould be a silent no-op (the exact trap the comment above warns about). A unique value (e.g.uniqueFileName('usage')oruniqueDirName('usage')) would make this unconditionally safe regardless of server state. Non-blocking in CI where a fresh container runs per job, but cheap to harden.