mcp-tool-policy.test.ts5.1 KBView on GitHub
import {
  buildPolicyFromRows,
  canSubmitConnectionForm,
  countAllowed,
  countNewTools,
  describeArgumentRule,
  needsPermissionReview,
  patchToolRow,
  toolRowsFromProbe,
  type McpToolRow,
} from '@/modules/integrations/mcp-tool-policy';

const row = (over: Partial<McpToolRow> = {}): McpToolRow => ({
  name: 'create_payment_link',
  description: 'Create a payment link',
  allowed: false,
  ...over,
});

describe('toolRowsFromProbe', () => {
  it('starts every discovered tool denied', () => {
    const rows = toolRowsFromProbe([{ name: 'a', description: 'A' }, { name: 'b' }]);
    expect(rows).toEqual([
      { name: 'a', description: 'A', allowed: false },
      { name: 'b', description: '', allowed: false },
    ]);
  });
});

describe('patchToolRow', () => {
  it('patches by name and never inserts an unknown tool', () => {
    const rows = [row({ name: 'a' }), row({ name: 'b' })];
    expect(patchToolRow(rows, 'b', { allowed: true })[1].allowed).toBe(true);
    expect(patchToolRow(rows, 'zzz', { allowed: true })).toHaveLength(2);
    expect(patchToolRow(rows, 'zzz', { allowed: true }).some((r) => r.allowed)).toBe(false);
  });
});

describe('buildPolicyFromRows', () => {
  it('always writes allowlist mode — saving the checklist is the migration off allow_all', () => {
    expect(buildPolicyFromRows([row()]).mode).toBe('allowlist');
  });

  it('carries the tick, the instruction and the approval flag onto the rule', () => {
    const policy = buildPolicyFromRows([
      row({ allowed: true, instruction: '  Annual plans only.  ', requireApproval: true }),
    ]);
    expect(policy.rules[0]).toMatchObject({
      toolName: 'create_payment_link',
      allowed: true,
      instruction: 'Annual plans only.',
      requireApproval: true,
    });
  });

  it('keeps a denied tool in the rules rather than dropping it', () => {
    // A dropped rule reads as "never discovered" and comes back badged New — the
    // badge has to mean a genuinely new tool, not one the user declined.
    const policy = buildPolicyFromRows([row({ name: 'create_refund', allowed: false })]);
    expect(policy.rules).toEqual([
      { toolName: 'create_refund', allowed: false, description: 'Create a payment link' },
    ]);
  });

  it('preserves argument rules and pinned arguments verbatim', () => {
    const policy = buildPolicyFromRows([
      row({
        allowed: true,
        argumentRules: [{ field: 'currency', oneOf: ['usd'] }],
        pinnedArguments: { livemode: false },
      }),
    ]);
    expect(policy.rules[0].argumentRules).toEqual([{ field: 'currency', oneOf: ['usd'] }]);
    expect(policy.rules[0].pinnedArguments).toEqual({ livemode: false });
  });

  it('carries the discovery stamp through without inventing one', () => {
    expect(buildPolicyFromRows([row()], { discoveredAt: '2026-08-25T00:00:00Z' }).discoveredAt).toBe(
      '2026-08-25T00:00:00Z',
    );
    expect(buildPolicyFromRows([row()]).discoveredAt).toBeUndefined();
  });
});

describe('needsPermissionReview', () => {
  it('flags an allow_all connection — every pre-policy connection was backfilled to it', () => {
    expect(needsPermissionReview({ mode: 'allow_all', allowedCount: 0, ruleCount: 0 })).toBe(true);
  });

  it('does not flag an allowlist connection or a missing digest', () => {
    expect(needsPermissionReview({ mode: 'allowlist', allowedCount: 1, ruleCount: 3 })).toBe(false);
    expect(needsPermissionReview(null)).toBe(false);
    expect(needsPermissionReview(undefined)).toBe(false);
  });
});

describe('counts', () => {
  it('counts allowed and new tools', () => {
    const rows = [row({ name: 'a', allowed: true }), row({ name: 'b', isNew: true })];
    expect(countAllowed(rows)).toBe(1);
    expect(countNewTools(rows)).toBe(1);
  });
});

describe('describeArgumentRule', () => {
  it('renders each constraint kind as prose', () => {
    expect(describeArgumentRule({ field: 'currency', oneOf: ['usd', 'eur'] })).toBe(
      'currency is one of usd, eur',
    );
    expect(describeArgumentRule({ field: 'op', matches: '^Get' })).toBe('op matches ^Get');
    expect(describeArgumentRule({ field: 'id', required: true })).toBe('id is required');
    expect(describeArgumentRule({ field: 'id' })).toBe('id');
  });
});

describe('canSubmitConnectionForm', () => {
  const base = { name: 'Stripe', serverUrl: 'https://mcp.stripe.com', isSaving: false } as const;

  it('permits saving after a failed probe — a briefly-down server must not block a connection', () => {
    expect(canSubmitConnectionForm({ ...base, probeStatus: 'failed' })).toBe(true);
  });

  it('permits saving before any probe', () => {
    expect(canSubmitConnectionForm({ ...base, probeStatus: 'idle' })).toBe(true);
  });

  it('blocks only on missing fields, an in-flight probe, or an in-flight save', () => {
    expect(canSubmitConnectionForm({ ...base, probeStatus: 'probing' })).toBe(false);
    expect(canSubmitConnectionForm({ ...base, probeStatus: 'ready', isSaving: true })).toBe(false);
    expect(canSubmitConnectionForm({ ...base, name: '  ', probeStatus: 'ready' })).toBe(false);
    expect(canSubmitConnectionForm({ ...base, serverUrl: '', probeStatus: 'ready' })).toBe(false);
  });
});