mcp-connection-form.test.tsx4.0 KBView on GitHub
import type { McpProbeStatus, McpToolRow } from '@/modules/integrations/mcp-tool-policy';
import { McpConnectionForm } from '@/modules/integrations/mcp-connection-form';
import { fireEvent, render, screen } from '@testing-library/react';

const noop = () => {};

function renderForm(
  over: Partial<{
    mode: 'add' | 'edit';
    probeStatus: McpProbeStatus;
    probeError: string | null;
    tools: McpToolRow[];
    onSubmit: () => void;
    isSaving: boolean;
  }> = {},
) {
  const onSubmit = over.onSubmit ?? jest.fn();
  render(
    <McpConnectionForm
      mode={over.mode ?? 'add'}
      values={{
        name: 'Stripe',
        serverUrl: 'https://mcp.stripe.com',
        authorizationHeader: '',
        instructions: '',
      }}
      onChange={noop}
      probeStatus={over.probeStatus ?? 'idle'}
      probeError={over.probeError ?? null}
      tools={over.tools ?? []}
      onProbe={noop}
      onToggleTool={noop}
      onToolInstructionChange={noop}
      onSubmit={onSubmit}
      onCancel={noop}
      isSaving={over.isSaving ?? false}
    />,
  );
  return { onSubmit };
}

describe('McpConnectionForm', () => {
  it('lets the connection be saved after a failed probe', () => {
    // A server that is asleep or slow to warm is a normal state. Blocking the save
    // would strand the user with no connection to retry the probe from — and the
    // saved connection grants nothing, because it is deny-by-default.
    const { onSubmit } = renderForm({
      probeStatus: 'failed',
      probeError: 'fetch failed: ECONNREFUSED',
    });

    const connect = screen.getByRole('button', { name: 'Connect' });
    expect(connect).toBeEnabled();
    fireEvent.click(connect);
    expect(onSubmit).toHaveBeenCalled();
  });

  it('shows the probe error and says saving is still safe', () => {
    renderForm({ probeStatus: 'failed', probeError: 'fetch failed: ECONNREFUSED' });
    const alert = screen.getByRole('alert');
    expect(alert).toHaveTextContent('Could not reach this server');
    expect(alert).toHaveTextContent('fetch failed: ECONNREFUSED');
    expect(alert).toHaveTextContent(/no tool is enabled until you enable it/i);
  });

  it('shows the discovered tools as a checklist before the connection is saved', () => {
    renderForm({
      probeStatus: 'ready',
      tools: [
        { name: 'create_payment_link', description: 'Create a link', allowed: false },
        { name: 'create_refund', description: 'Refund a charge', allowed: false },
      ],
    });
    expect(screen.getByText('0 of 2 tools enabled')).toBeInTheDocument();
    expect(screen.getByRole('checkbox', { name: 'Allow create_payment_link' })).not.toBeChecked();
    expect(screen.getByRole('checkbox', { name: 'Allow create_refund' })).not.toBeChecked();
  });

  it('grants nothing before anything is probed — no checklist, just the check', () => {
    // This used to assert the sentence "New connections start with every tool denied", which
    // sat under the label as a hint. The hint is gone (the button beside the label says what to
    // do), so the guarantee is asserted where it actually lives: until a probe returns there is
    // no tool list at all, and therefore nothing that could be enabled.
    renderForm({ probeStatus: 'idle' });
    expect(screen.getByRole('button', { name: /Check available tools/i })).toBeInTheDocument();
    expect(screen.queryByRole('checkbox', { name: /^Allow / })).not.toBeInTheDocument();
    expect(screen.queryByText(/tools enabled/i)).not.toBeInTheDocument();
  });

  it('blocks submission only while a probe or a save is in flight', () => {
    renderForm({ probeStatus: 'probing' });
    expect(screen.getByRole('button', { name: 'Connect' })).toBeDisabled();
  });

  it('does not offer setup-time discovery when editing — permissions live on the connection', () => {
    renderForm({ mode: 'edit' });
    expect(
      screen.queryByRole('button', { name: /Check available tools/i }),
    ).not.toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Save changes' })).toBeEnabled();
  });
});