import React from 'react'; import { render, screen, waitFor, fireEvent } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom'; import AgentOperationsBanner from '@/components/doormile/AgentOperationsBanner'; import * as scanModule from '@/lib/assistant/scan'; import { AgentFactory } from '@/lib/assistant/agent/AgentFactory'; import { SkillRegistry } from '@/lib/assistant/skills/SkillRegistry'; jest.mock('lucide-react', () => new Proxy({}, { get: (_t, prop) => (prop === '__esModule' ? true : (props) => ) }) ); jest.mock('@/lib/assistant/scan'); jest.mock('@/lib/assistant/agent/AgentFactory'); const createTestQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false } } }); function Where() { const loc = useLocation(); return
{loc.pathname + loc.search}
; } const renderBanner = (props = {}) => render( } /> ); const SLA_FINDING = { id: 'sla_breached', skillId: 'skill_sla_guardian', severity: 'critical', title: '1 parcel is past the promised delivery time', why: 'Estimated delivery time has passed.', count: 1, rows: [{ bookingid: 101, bookingno: 'BK-101' }], detail: ['BK-101 — 35 min overdue'], proposal: { label: 'Notify the assigned riders', tool: 'notifyRider', scope: [{ bookingid: 101 }], blastRadius: 'Sends a message per rider.', }, }; describe('AgentOperationsBanner', () => { beforeEach(() => { jest.clearAllMocks(); SkillRegistry.clearRegistryConfig(); }); it('says nothing is flagged, naming how many skills looked', async () => { scanModule.scanBookings.mockResolvedValue({ rows: [], truncated: false }); AgentFactory.synthesizeDefaultAgent.mockReturnValue({ evaluateTelemetry: () => [] }); renderBanner(); await waitFor(() => { expect(screen.getByText(/Nothing flagged by the 5 enabled skills/i)).toBeInTheDocument(); }); expect(screen.getByRole('button', { name: /Configure skills/i })).toBeInTheDocument(); }); it('renders remediation cards when findings are detected', async () => { scanModule.scanBookings.mockResolvedValue({ rows: [{ bookingid: 101 }], truncated: false }); AgentFactory.synthesizeDefaultAgent.mockReturnValue({ evaluateTelemetry: () => [SLA_FINDING] }); renderBanner({ title: 'Custom Title' }); await waitFor(() => { expect(screen.getByText(/Custom Title/i)).toBeInTheDocument(); expect(screen.getByText(/1 parcel is past the promised delivery time/i)).toBeInTheDocument(); }); }); it('filters findings by category', async () => { scanModule.scanBookings.mockResolvedValue({ rows: [{ bookingid: 101 }], truncated: false }); AgentFactory.synthesizeDefaultAgent.mockReturnValue({ evaluateTelemetry: () => [ { ...SLA_FINDING, proposal: undefined }, { id: 'rider_critical_battery', skillId: 'skill_rider_battery_safety', // category: rider_safety severity: 'warning', title: 'Rider battery critical', why: 'Low battery', count: 1, rows: [{ bookingid: 102 }], }, ], }); renderBanner({ category: 'rider_safety', title: 'Rider Safety Only' }); await waitFor(() => { expect(screen.getByText(/Rider Safety Only/i)).toBeInTheDocument(); expect(screen.getByText(/Rider battery critical/i)).toBeInTheDocument(); expect(screen.queryByText(/1 parcel is past the promised delivery time/i)).toBeNull(); }); }); // The branch opened a localStorage-only settings modal here. Settings now // live in the agent registry, edited on one page. it('"Configure skills" goes to Settings → Skills & Tools', async () => { scanModule.scanBookings.mockResolvedValue({ rows: [], truncated: false }); AgentFactory.synthesizeDefaultAgent.mockReturnValue({ evaluateTelemetry: () => [] }); renderBanner(); fireEvent.click(await screen.findByRole('button', { name: /Configure skills/i })); expect(screen.getByTestId('where')).toHaveTextContent('/doormile/settings?tab=skills'); }); it('says when it is running on default thresholds, and stops once the registry answers', async () => { scanModule.scanBookings.mockResolvedValue({ rows: [], truncated: false }); AgentFactory.synthesizeDefaultAgent.mockReturnValue({ evaluateTelemetry: () => [] }); renderBanner(); expect(await screen.findByText(/Running on default thresholds/i)).toBeInTheDocument(); SkillRegistry.applyRegistryConfig([{ skillid: 'skill_sla_guardian', enabled: true, thresholds: {} }]); await waitFor(() => expect(screen.queryByText(/Running on default thresholds/i)).toBeNull()); }); it('never calls anything autonomous', async () => { scanModule.scanBookings.mockResolvedValue({ rows: [], truncated: false }); AgentFactory.synthesizeDefaultAgent.mockReturnValue({ evaluateTelemetry: () => [SLA_FINDING] }); renderBanner(); await screen.findByText(/1 parcel is past the promised delivery time/i); expect(screen.queryByText(/autonomous/i)).toBeNull(); expect(screen.queryByText(/self-healing/i)).toBeNull(); }); });