// FE-COMP-INTEGRATIONS-001 to FE-COMP-INTEGRATIONS-032 import { render, screen, waitFor } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { server } from '../../../tests/helpers/msw/server'; import { useAuthStore } from '../../store/authStore'; import { useAddonStore } from '../../store/addonStore'; import { resetAllStores, seedStore } from '../../../tests/helpers/store'; import { buildUser } from '../../../tests/helpers/factories'; import { ToastContainer } from '../shared/Toast'; import IntegrationsTab from './IntegrationsTab'; function enableMcp() { seedStore(useAddonStore, { addons: [{ id: 'mcp', name: 'MCP', type: 'integration', icon: '', enabled: true }], loaded: true, loadAddons: vi.fn(), }); } const clipboardWriteText = vi.fn().mockResolvedValue(undefined); beforeAll(() => { Object.defineProperty(navigator, 'clipboard', { value: { writeText: clipboardWriteText }, configurable: true, writable: true, }); }); beforeEach(() => { clipboardWriteText.mockClear(); resetAllStores(); vi.clearAllMocks(); seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true }); seedStore(useAddonStore, { addons: [], loaded: true, loadAddons: vi.fn(), }); server.use( http.get('/api/auth/mcp-tokens', () => HttpResponse.json({ tokens: [] })), http.get('/api/addons', () => HttpResponse.json({ addons: [] })), http.get('/api/oauth/clients', () => HttpResponse.json({ clients: [] })), http.get('/api/oauth/sessions', () => HttpResponse.json({ sessions: [] })), ); }); describe('IntegrationsTab', () => { it('FE-COMP-INTEGRATIONS-001: renders without crashing (MCP disabled)', () => { render(); expect(document.body).toBeInTheDocument(); }); it('FE-COMP-INTEGRATIONS-002: MCP section is hidden when mcp addon is disabled', () => { render(); expect(screen.queryByText('MCP Configuration')).toBeNull(); }); it('FE-COMP-INTEGRATIONS-003: MCP section is visible when mcp addon is enabled', async () => { enableMcp(); render(); await screen.findByText('MCP Configuration'); }); it('FE-COMP-INTEGRATIONS-004: MCP endpoint URL is displayed', async () => { enableMcp(); render(); await screen.findByText('MCP Configuration'); const codeEl = document.querySelector('code'); expect(codeEl).not.toBeNull(); expect(codeEl!.textContent).toContain('/mcp'); }); it('FE-COMP-INTEGRATIONS-005: JSON config block is rendered when expanded', async () => { const user = userEvent.setup(); enableMcp(); render(); await screen.findByText('MCP Configuration'); // Config is collapsed by default — no
 yet
    expect(document.querySelector('pre')).toBeNull();
    // Expand by clicking the "Client Configuration" toggle
    await user.click(screen.getByRole('button', { name: /Client Configuration/i }));
    const preEl = document.querySelector('pre');
    expect(preEl).not.toBeNull();
    expect(preEl!.textContent).toContain('mcpServers');
  });

  it('FE-COMP-INTEGRATIONS-006: "no tokens" message shown when token list is empty', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await screen.findByText('No tokens yet. Create one to connect MCP clients.');
  });

  it('FE-COMP-INTEGRATIONS-007: token list renders when tokens exist', async () => {
    server.use(
      http.get('/api/auth/mcp-tokens', () =>
        HttpResponse.json({
          tokens: [
            { id: 1, name: 'My Token', token_prefix: 'tk_aaa', created_at: '2025-01-01T00:00:00.000Z', last_used_at: null },
            { id: 2, name: 'Other Token', token_prefix: 'tk_bbb', created_at: '2025-01-01T00:00:00.000Z', last_used_at: null },
          ],
        }),
      ),
    );
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await screen.findByText('My Token');
    await screen.findByText('Other Token');
  });

  it('FE-COMP-INTEGRATIONS-008: clicking "Create New Token" button opens the modal', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    const createBtn = screen.getByRole('button', { name: /Create New Token/i });
    await user.click(createBtn);
    await screen.findByText('Create API Token');
  });

  it('FE-COMP-INTEGRATIONS-009: Create button in modal is disabled when name is empty', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await user.click(screen.getByRole('button', { name: /Create New Token/i }));
    await screen.findByText('Create API Token');
    const modalCreateBtn = screen.getByRole('button', { name: /^Create Token$/i });
    expect(modalCreateBtn).toBeDisabled();
  });

  it('FE-COMP-INTEGRATIONS-010: Create button in modal becomes enabled when name is typed', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await user.click(screen.getByRole('button', { name: /Create New Token/i }));
    await screen.findByText('Create API Token');
    const input = screen.getByPlaceholderText(/Claude Desktop/i);
    await user.type(input, 'My API token');
    const modalCreateBtn = screen.getByRole('button', { name: /^Create Token$/i });
    expect(modalCreateBtn).not.toBeDisabled();
  });

  it('FE-COMP-INTEGRATIONS-011: creating a token calls the API and shows the raw token', async () => {
    server.use(
      http.post('/api/auth/mcp-tokens', () =>
        HttpResponse.json({
          token: {
            id: 1,
            name: 'test',
            token_prefix: 'tk_abc',
            created_at: '2025-01-01T00:00:00.000Z',
            raw_token: 'tk_abc...full_secret_token',
          },
        }),
      ),
    );
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await user.click(screen.getByRole('button', { name: /Create New Token/i }));
    await screen.findByText('Create API Token');
    const input = screen.getByPlaceholderText(/Claude Desktop/i);
    await user.type(input, 'test');
    await user.click(screen.getByRole('button', { name: /^Create Token$/i }));
    // Raw token should be displayed
    await screen.findByText(/tk_abc\.\.\.full_secret_token/);
    // Warning about one-time display
    expect(screen.getByText(/only be shown once/i)).toBeInTheDocument();
  });

  it('FE-COMP-INTEGRATIONS-012: "Done" button closes the token-created modal', async () => {
    server.use(
      http.post('/api/auth/mcp-tokens', () =>
        HttpResponse.json({
          token: {
            id: 1,
            name: 'test',
            token_prefix: 'tk_abc',
            created_at: '2025-01-01T00:00:00.000Z',
            raw_token: 'tk_abc...full_secret_token',
          },
        }),
      ),
    );
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await user.click(screen.getByRole('button', { name: /Create New Token/i }));
    await screen.findByText('Create API Token');
    await user.type(screen.getByPlaceholderText(/Claude Desktop/i), 'test');
    await user.click(screen.getByRole('button', { name: /^Create Token$/i }));
    await screen.findByText('Token Created');
    await user.click(screen.getByRole('button', { name: /^Done$/i }));
    await waitFor(() => {
      expect(screen.queryByText('Token Created')).toBeNull();
    });
  });

  it('FE-COMP-INTEGRATIONS-013: clicking the delete button next to a token opens the confirm modal', async () => {
    server.use(
      http.get('/api/auth/mcp-tokens', () =>
        HttpResponse.json({
          tokens: [
            { id: 1, name: 'Delete Me', token_prefix: 'tk_del', created_at: '2025-01-01T00:00:00.000Z', last_used_at: null },
          ],
        }),
      ),
    );
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await screen.findByText('Delete Me');
    await user.click(screen.getByTitle('Delete Token'));
    await screen.findByText('This token will stop working immediately. Any MCP client using it will lose access.');
    expect(screen.getByRole('button', { name: /^Cancel$/i })).toBeInTheDocument();
  });

  it('FE-COMP-INTEGRATIONS-014: confirming deletion calls DELETE API and removes token from list', async () => {
    let deleteCalled = false;
    server.use(
      http.get('/api/auth/mcp-tokens', () =>
        HttpResponse.json({
          tokens: [
            { id: 1, name: 'Delete Me', token_prefix: 'tk_del', created_at: '2025-01-01T00:00:00.000Z', last_used_at: null },
          ],
        }),
      ),
      http.delete('/api/auth/mcp-tokens/1', () => {
        deleteCalled = true;
        return HttpResponse.json({ success: true });
      }),
    );
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await screen.findByText('Delete Me');
    await user.click(screen.getByTitle('Delete Token'));
    // There are two "Delete Token" buttons: the trash icon (title) and the confirm button in modal
    const deleteButtons = await screen.findAllByRole('button', { name: /^Delete Token$/i });
    // Click the one in the modal (last one, or the standalone one without title attribute)
    const confirmBtn = deleteButtons.find(btn => !btn.title);
    await user.click(confirmBtn ?? deleteButtons[deleteButtons.length - 1]);
    expect(deleteCalled).toBe(true);
    await waitFor(() => {
      expect(screen.queryByText('Delete Me')).toBeNull();
    });
  });

  it('FE-COMP-INTEGRATIONS-015: copying endpoint URL calls clipboard.writeText', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    // Spy after userEvent.setup() may have replaced navigator.clipboard
    const writeSpy = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(undefined);
    const copyBtns = screen.getAllByTitle('Copy');
    await user.click(copyBtns[0]);
    expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('/mcp'));
  });

  it('FE-COMP-INTEGRATIONS-016: copy button shows checkmark icon after copy', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(undefined);
    const copyBtns = screen.getAllByTitle('Copy');
    await user.click(copyBtns[0]);
    await waitFor(() => {
      // After copy, icon changes to Check (green). The button should contain an svg with text-green-500
      const btn = copyBtns[0];
      const svg = btn.querySelector('svg');
      expect(svg).toHaveClass('text-green-500');
    });
  });

  it('FE-COMP-INTEGRATIONS-017: cancel button in delete confirm modal closes it without API call', async () => {
    let deleteCalled = false;
    server.use(
      http.get('/api/auth/mcp-tokens', () =>
        HttpResponse.json({
          tokens: [
            { id: 1, name: 'Cancel Token', token_prefix: 'tk_can', created_at: '2025-01-01T00:00:00.000Z', last_used_at: null },
          ],
        }),
      ),
      http.delete('/api/auth/mcp-tokens/1', () => {
        deleteCalled = true;
        return HttpResponse.json({ success: true });
      }),
    );
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await screen.findByText('Cancel Token');
    await user.click(screen.getByTitle('Delete Token'));
    await screen.findByRole('button', { name: /^Cancel$/i });
    await user.click(screen.getByRole('button', { name: /^Cancel$/i }));
    await waitFor(() => {
      expect(screen.queryByText('This token will stop working immediately. Any MCP client using it will lose access.')).toBeNull();
    });
    expect(deleteCalled).toBe(false);
  });

  it('FE-COMP-INTEGRATIONS-018: pressing Enter in the token name input triggers creation', async () => {
    let postCalled = false;
    server.use(
      http.post('/api/auth/mcp-tokens', () => {
        postCalled = true;
        return HttpResponse.json({
          token: {
            id: 1,
            name: 'enter-test',
            token_prefix: 'tk_ent',
            created_at: '2025-01-01T00:00:00.000Z',
            raw_token: 'tk_ent...full',
          },
        });
      }),
    );
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await user.click(screen.getByRole('button', { name: /Create New Token/i }));
    await screen.findByText('Create API Token');
    const input = screen.getByPlaceholderText(/Claude Desktop/i);
    await user.type(input, 'enter-test');
    await user.keyboard('{Enter}');
    await waitFor(() => {
      expect(postCalled).toBe(true);
    });
  });

  it('FE-COMP-INTEGRATIONS-019: default tab is OAuth 2.1 Clients — OAuth hint visible, token list hidden', async () => {
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    // OAuth hint is visible on the default tab
    expect(screen.getByText(/Register OAuth 2\.1 clients/i)).toBeInTheDocument();
    // API Tokens "no tokens" message is not rendered
    expect(screen.queryByText('No tokens yet. Create one to connect MCP clients.')).toBeNull();
  });

  it('FE-COMP-INTEGRATIONS-020: switching tabs toggles content visibility', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    // Default: OAuth hint visible, token list absent
    expect(screen.getByText(/Register OAuth 2\.1 clients/i)).toBeInTheDocument();
    expect(screen.queryByText('No tokens yet. Create one to connect MCP clients.')).toBeNull();
    // Switch to API Tokens tab
    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
    await screen.findByText('No tokens yet. Create one to connect MCP clients.');
    expect(screen.queryByText(/Register OAuth 2\.1 clients/i)).toBeNull();
    // Switch back to OAuth tab
    await user.click(screen.getByRole('button', { name: /OAuth 2\.1 Clients/i }));
    await screen.findByText(/Register OAuth 2\.1 clients/i);
    expect(screen.queryByText('No tokens yet. Create one to connect MCP clients.')).toBeNull();
  });

  it('FE-COMP-INTEGRATIONS-021: OAuth client list renders when clients exist', async () => {
    server.use(
      http.get('/api/oauth/clients', () =>
        HttpResponse.json({
          clients: [
            {
              id: 'client-1',
              client_id: 'clid-abc',
              name: 'My OAuth App',
              redirect_uris: ['http://localhost'],
              allowed_scopes: ['trips:read', 'places:read'],
              created_at: '2025-01-01T00:00:00Z',
            },
          ],
        })
      )
    );
    enableMcp();
    render();
    await screen.findByText('My OAuth App');
    expect(screen.getByText(/clid-abc/)).toBeInTheDocument();
  });

  it('FE-COMP-INTEGRATIONS-022: scope expansion toggle shows more/fewer scopes', async () => {
    const user = userEvent.setup();
    const scopes = ['trips:read', 'trips:write', 'places:read', 'places:write', 'budget:read', 'budget:write', 'packing:read'];
    server.use(
      http.get('/api/oauth/clients', () =>
        HttpResponse.json({
          clients: [
            { id: 'c1', client_id: 'cid', name: 'Big App', redirect_uris: ['http://localhost'], allowed_scopes: scopes, created_at: '2025-01-01T00:00:00Z' },
          ],
        })
      )
    );
    enableMcp();
    render();
    await screen.findByText('Big App');
    // "+2 more" button visible (7 scopes, 5 shown)
    const moreBtn = screen.getByText(/^\+\d+$/);
    await user.click(moreBtn);
    // Show less / collapse button now visible
    expect(screen.getByText('−')).toBeInTheDocument();
  });

  it('FE-COMP-INTEGRATIONS-023: active OAuth sessions section renders when sessions exist', async () => {
    server.use(
      http.get('/api/oauth/sessions', () =>
        HttpResponse.json({
          sessions: [
            {
              id: 10,
              client_name: 'Claude Desktop',
              scopes: ['trips:read'],
              access_token_expires_at: '2025-12-31T00:00:00Z',
            },
          ],
        })
      )
    );
    enableMcp();
    render();
    await screen.findByText('Claude Desktop');
    expect(screen.getByText(/trips:read/)).toBeInTheDocument();
  });

  it('FE-COMP-INTEGRATIONS-024: Create OAuth Client modal opens and shows presets', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /New Client/i }));
    await screen.findByText('Register OAuth Client');
    expect(screen.getByText('Claude.ai')).toBeInTheDocument();
    expect(screen.getByText('Claude Desktop')).toBeInTheDocument();
  });

  it('FE-COMP-INTEGRATIONS-025: clicking a preset fills form fields', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /New Client/i }));
    await screen.findByText('Register OAuth Client');
    // Presets render as buttons — click "Claude.ai" preset
    const presetBtns = screen.getAllByRole('button', { name: /Claude\.ai/i });
    await user.click(presetBtns[0]);
    // Name field should be filled with 'Claude.ai'
    const nameInput = screen.getByPlaceholderText(/Claude Web, My MCP App/i);
    expect((nameInput as HTMLInputElement).value).toBe('Claude.ai');
  });

  it('FE-COMP-INTEGRATIONS-026: creating client shows success view with client_id and secret', async () => {
    const user = userEvent.setup();
    server.use(
      http.post('/api/oauth/clients', () =>
        HttpResponse.json({
          client: {
            id: 'new-id',
            client_id: 'clid-new',
            client_secret: 'secret-value',
            name: 'Test Client',
            redirect_uris: ['http://localhost'],
            allowed_scopes: ['trips:read'],
            created_at: '2025-01-01T00:00:00Z',
          },
        })
      )
    );
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /New Client/i }));
    await screen.findByText('Register OAuth Client');

    const nameInput = screen.getByPlaceholderText(/Claude Web, My MCP App/i);
    await user.type(nameInput, 'Test Client');
    const uriInput = screen.getByPlaceholderText(/https:\/\/your-app/i);
    await user.type(uriInput, 'http://localhost');
    await user.click(screen.getByRole('button', { name: /Register Client/i }));
    // Success view shows client credentials (there may be multiple matches in list + modal)
    await screen.findAllByText(/clid-new/);
    const secretEls = await screen.findAllByText(/secret-value/);
    expect(secretEls.length).toBeGreaterThan(0);
  });

  it('FE-COMP-INTEGRATIONS-027: Done button closes created-client modal', async () => {
    const user = userEvent.setup();
    server.use(
      http.post('/api/oauth/clients', () =>
        HttpResponse.json({
          client: {
            id: 'n2',
            client_id: 'clid-n2',
            client_secret: 'secret-n2',
            name: 'TC2',
            redirect_uris: ['http://localhost'],
            allowed_scopes: ['trips:read'],
            created_at: '2025-01-01T00:00:00Z',
          },
        })
      )
    );
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /New Client/i }));
    await screen.findByText('Register OAuth Client');
    await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'TC2');
    await user.type(screen.getByPlaceholderText(/https:\/\/your-app/i), 'http://localhost');
    await user.click(screen.getByRole('button', { name: /Register Client/i }));
    await screen.findAllByText(/clid-n2/);
    // Check the "Client Registered" modal title is visible before Done
    expect(screen.getByText('Client Registered')).toBeInTheDocument();
    await user.click(screen.getByRole('button', { name: /^Done$/i }));
    await waitFor(() => {
      expect(screen.queryByText('Client Registered')).toBeNull();
    });
  });

  it('FE-COMP-INTEGRATIONS-028: delete OAuth client confirmation removes client from list', async () => {
    const user = userEvent.setup();
    server.use(
      http.get('/api/oauth/clients', () =>
        HttpResponse.json({
          clients: [
            { id: 'del-1', client_id: 'cid-del', name: 'Delete Me', redirect_uris: ['http://localhost'], allowed_scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' },
          ],
        })
      ),
      http.delete('/api/oauth/clients/del-1', () => HttpResponse.json({ success: true }))
    );
    enableMcp();
    render(<>);
    await screen.findByText('Delete Me');
    await user.click(screen.getByTitle('Delete Client'));
    // Confirmation modal
    await screen.findByRole('heading', { name: 'Delete Client' });
    const confirmBtns = screen.getAllByRole('button', { name: /Delete Client/i });
    // Modal confirm button is last in DOM (modal renders after list)
    await user.click(confirmBtns[confirmBtns.length - 1]);
    await waitFor(() => {
      expect(screen.queryByText('Delete Me')).toBeNull();
    });
  });

  it('FE-COMP-INTEGRATIONS-029: rotate secret confirmation shows new secret', async () => {
    const user = userEvent.setup();
    server.use(
      http.get('/api/oauth/clients', () =>
        HttpResponse.json({
          clients: [
            { id: 'rot-1', client_id: 'cid-rot', name: 'Rotate Me', redirect_uris: ['http://localhost'], allowed_scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' },
          ],
        })
      ),
      http.post('/api/oauth/clients/rot-1/rotate', () =>
        HttpResponse.json({ client_secret: 'new-rotated-secret' })
      )
    );
    enableMcp();
    render();
    await screen.findByText('Rotate Me');
    await user.click(screen.getByTitle('Rotate Secret'));
    await screen.findByText('Rotate Secret');
    // Confirm — button text is 'Rotate'
    const rotateBtns = screen.getAllByRole('button', { name: /^Rotate$/i });
    await user.click(rotateBtns[rotateBtns.length - 1]);
    await screen.findByText(/new-rotated-secret/);
  });

  it('FE-COMP-INTEGRATIONS-030: revoke OAuth session removes it from list', async () => {
    const user = userEvent.setup();
    server.use(
      http.get('/api/oauth/sessions', () =>
        HttpResponse.json({
          sessions: [
            { id: 99, client_name: 'Revoke App', scopes: ['trips:read'], access_token_expires_at: '2025-12-31T00:00:00Z' },
          ],
        })
      ),
      http.delete('/api/oauth/sessions/99', () => HttpResponse.json({ success: true }))
    );
    enableMcp();
    render(<>);
    await screen.findByText('Revoke App');
    await user.click(screen.getByText('Revoke'));
    // Confirmation modal
    await screen.findByText('Revoke Session');
    const revokeBtns = screen.getAllByRole('button', { name: /^Revoke$/i });
    // Modal confirm button is last in DOM
    await user.click(revokeBtns[revokeBtns.length - 1]);
    await waitFor(() => {
      expect(screen.queryByText('Revoke App')).toBeNull();
    });
  });

  it('FE-COMP-INTEGRATIONS-031: Register Client button disabled when name or URI is empty', async () => {
    const user = userEvent.setup();
    enableMcp();
    render();
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /New Client/i }));
    await screen.findByText('Register OAuth Client');
    const createBtn = screen.getByRole('button', { name: /Register Client/i });
    expect(createBtn).toBeDisabled();
    // Type only name, not URI → still disabled
    await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'Test');
    expect(createBtn).toBeDisabled();
  });

  it('FE-COMP-INTEGRATIONS-032: error toast shown when create OAuth client fails', async () => {
    const user = userEvent.setup();
    server.use(
      http.post('/api/oauth/clients', () =>
        HttpResponse.json({ error: 'server error' }, { status: 500 })
      )
    );
    enableMcp();
    render(<>);
    await screen.findByText('MCP Configuration');
    await user.click(screen.getByRole('button', { name: /New Client/i }));
    await screen.findByText('Register OAuth Client');
    await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'Fail Client');
    await user.type(screen.getByPlaceholderText(/https:\/\/your-app/i), 'http://localhost');
    await user.click(screen.getByRole('button', { name: /Register Client/i }));
    await screen.findByText(/Failed to register/i);
  });
});