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();
});
});