diff --git a/frontend/src/modals/AgentDetailsModal.test.tsx b/frontend/src/modals/AgentDetailsModal.test.tsx index 1f3ccc26..603e9800 100644 --- a/frontend/src/modals/AgentDetailsModal.test.tsx +++ b/frontend/src/modals/AgentDetailsModal.test.tsx @@ -17,8 +17,11 @@ vi.mock('react-redux', () => ({ })); vi.mock('../api/services/userService', () => ({ default: mocks })); -// Tested on its own; it needs the store and the tool list. -vi.mock('../agents/ApiWriteAllowlist', () => ({ default: () => null })); +// Tested on its own; it needs the store and the tool list. Here it only has +// to show up (or not). +vi.mock('../agents/ApiWriteAllowlist', () => ({ + default: () =>
, +})); vi.mock('./ConfirmationModal', () => ({ default: ({ @@ -99,6 +102,33 @@ describe('AgentDetailsModal', () => { ); }); + // The allowlist acts on the owner's connected accounts; the server keeps + // it unchanged for anyone else, so only the owner sees it. + describe('API write allowlist', () => { + const allowlist = () => + document.querySelector('[data-testid="api-write-allowlist"]'); + + it('shows it to the owner of an agent with a key', async () => { + await render({ status: 'published', key: 'k-1', access: 'owner' }); + expect(allowlist()).not.toBeNull(); + }); + + it('hides it from an editor, even with a key', async () => { + await render({ + status: 'published', + key: 'k-1', + access: 'editor', + allowed_actions: ['view', 'edit', 'manage_access_details'], + }); + expect(allowlist()).toBeNull(); + }); + + it('hides it until the agent has a key', async () => { + await render({ status: 'published', access: 'owner' }); + expect(allowlist()).toBeNull(); + }); + }); + it('shows a refused public link in an alert', async () => { mocks.shareAgent.mockReturnValue( respond({ success: false, message: 'Not allowed' }, false), diff --git a/frontend/src/settings/Teams.test.tsx b/frontend/src/settings/Teams.test.tsx index 218403a7..e64b5214 100644 --- a/frontend/src/settings/Teams.test.tsx +++ b/frontend/src/settings/Teams.test.tsx @@ -1,6 +1,6 @@ import { act } from 'react'; import { createRoot, type Root } from 'react-dom/client'; -import { MemoryRouter } from 'react-router-dom'; +import { MemoryRouter, useLocation } from 'react-router-dom'; // A JWT whose payload is {"sub":"me"}. const TOKEN = `x.${btoa(JSON.stringify({ sub: 'me' }))}.y`; @@ -183,6 +183,10 @@ describe('Teams page', () => { document.body.innerHTML = ''; }); + function Where() { + return
{useLocation().pathname}
; + } + const render = async () => { act(() => { root.render( @@ -190,6 +194,7 @@ describe('Teams page', () => { initialEntries={[{ pathname: '/teams', state: { openTeamId: 't1' } }]} > + , ); }); @@ -362,6 +367,27 @@ describe('Teams page', () => { ); }); + it('opens a shared source on the Knowledge page', async () => { + listGrants.mockResolvedValue({ + team_role: 'team_member', + grants: [grant({ caller: VIEWER })], + }); + await render(); + await openDrawer(); + const open = Array.from( + body().querySelectorAll( + '[data-slot="sheet-content"] button', + ), + ).find((b) => + b.textContent?.startsWith('settings.teams.drawer.open(type='), + )!; + act(() => open.click()); + await flush(); + expect(body().querySelector('[data-testid="where"]')?.textContent).toBe( + '/settings/knowledge', + ); + }); + it('sends target_user_id when removing a per-member grant', async () => { listGrants.mockResolvedValue({ team_role: 'team_admin', diff --git a/frontend/src/settings/Teams.tsx b/frontend/src/settings/Teams.tsx index ddc8f203..da48633d 100644 --- a/frontend/src/settings/Teams.tsx +++ b/frontend/src/settings/Teams.tsx @@ -737,7 +737,7 @@ export default function Teams() { return agent ? agentEditPathFor(agent) : agentEditPath(r.id); } case 'source': - return '/settings/sources'; + return '/settings/knowledge'; case 'tool': return '/settings/tools'; case 'prompt':