Browse documentation

Test Specs

RankFlow AI — Admin Dashboard & Observability Test Specification

- [ ] AdminShell renders with sidebar, header, and content area for ADMIN role

docs/test-specs/TEST-admin-observability.md
On this page

Version: 1.0.0
Date: 2026-06-13
Target Specs: docs/specs/admin-dashboard-ui.md, docs/business_flow_map.md Section 6
Systems Under Test: Next.js Admin Shell, tRPC Admin Router, BullMQ Job Monitor, KPI Aggregations, Alert Engine
Test Tooling: Vitest, React Testing Library, msw, ioredis-mock, @testing-library/user-event, bullmq test helpers


1. Admin Layout & Access Control Tests#

1.1 Unit Tests — Layout Components#

Test Input Expected Output File
admin-shell.render role: "ADMIN", pathname: "/admin" Renders AdminSidebar, AdminHeader, and PageContent without error src/__tests__/unit/admin/layout.test.tsx
admin-sidebar.navigation currentRoute: "/admin/clients" All 9 nav items render; "All Clients" has aria-current="page" src/__tests__/unit/admin/Sidebar.test.tsx
admin-sidebar.collapsible isCollapsed: true Sidebar width = 64px; nav labels hidden; icons visible src/__tests__/unit/admin/Sidebar.test.tsx
admin-header.breadcrumbs pathname: "/admin/clients/prac_123" Breadcrumbs: "Dashboard > Clients > Dr. Smith Dental" src/__tests__/unit/admin/Header.test.tsx
admin-header.notifications unreadCount: 3 Notification bell shows badge with "3"; dropdown opens on click src/__tests__/unit/admin/Header.test.tsx
admin-header.usermenu user: { name: "Admin", email: "admin@rankflow.in" } Dropdown shows user name, email, "Sign out" link src/__tests__/unit/admin/Header.test.tsx
stat-card.render { label: "MRR", value: 480000, prefix: "₹" } Displays "₹4,80,000" with "MRR" label; color variant applied src/__tests__/unit/admin/StatCard.test.tsx
status-badge.active status: "ACTIVE" Green badge with "Active" text src/__tests__/unit/admin/StatusBadge.test.tsx
status-badge.trial status: "TRIAL" Amber badge with "Trial" text src/__tests__/unit/admin/StatusBadge.test.tsx
status-badge.churned status: "CHURNED" Red badge with "Churned" text src/__tests__/unit/admin/StatusBadge.test.tsx
data-table.sort Click column header "Name" Table rows re-sort ascending; click again = descending src/__tests__/unit/admin/DataTable.test.tsx
data-table.filter Type "dental" in search filter Only rows with "dental" in name remain visible; count = 2 src/__tests__/unit/admin/DataTable.test.tsx
data-table.pagination totalRows: 50, pageSize: 10 Page 1 shows rows 1-10; "Next" enabled; page indicator "1 / 5" src/__tests__/unit/admin/DataTable.test.tsx
confirm-dialog.render isOpen: true, title: "Suspend Client", description: "..." Dialog renders with title, description, "Cancel" and "Confirm" buttons src/__tests__/unit/admin/ConfirmDialog.test.tsx
confirm-dialog.confirm Click "Confirm" button onConfirm callback fired; dialog closes src/__tests__/unit/admin/ConfirmDialog.test.tsx
filter-bar.date-range Select "Last 7 days" DateRangePicker shows start/end dates; onFilterChange called with range src/__tests__/unit/admin/FilterBar.test.tsx
practice-select.render practices: [{ id: "prac_1", name: "Dr. A" }, { id: "prac_2", name: "Dr. B" }] Dropdown renders practice names; searchable src/__tests__/unit/admin/PracticeSelect.test.tsx
json-viewer.render data: { jobId: "job_123", payload: { practiceId: "prac_1" } } Syntax-highlighted JSON with collapsible nodes src/__tests__/unit/admin/JsonViewer.test.tsx

1.2 Integration Tests — Access Control & Role Enforcement#

Test Setup Action Assertion File
access.admin-allowed Session with role: "ADMIN" Navigate to /admin Page renders 200; AdminShell mounts; no redirect src/__tests__/integration/admin/access.test.ts
access.client-blocked Session with role: "CLIENT" Navigate to /admin Redirects to /dashboard with 302; AdminShell never mounts src/__tests__/integration/admin/access.test.ts
access.editor-blocked Session with role: "EDITOR" Navigate to /admin Redirects to /dashboard with 302 src/__tests__/integration/admin/access.test.ts
access.unauthenticated No session Navigate to /admin Redirects to /login?redirect=/admin src/__tests__/integration/admin/access.test.ts
api.admin-procedure-allowed x-practice-id: admin, authenticated admin user Call admin.getKPIs tRPC query Returns AdminKPIs object with all fields populated src/__tests__/integration/admin/api.access.test.ts
api.admin-procedure-blocked Authenticated client user Call admin.getKPIs tRPC query Returns TRPCError with code FORBIDDEN (403) src/__tests__/integration/admin/api.access.test.ts
api.admin-procedure-unauthenticated No session Call admin.listClients tRPC query Returns TRPCError with code UNAUTHORIZED (401) src/__tests__/integration/admin/api.access.test.ts
layout.mobile-drawer Viewport 375px width Render /admin Sidebar hidden; hamburger menu visible; drawer opens on click src/__tests__/integration/admin/responsive.test.tsx
layout.tablet-collapsible Viewport 800px width Render /admin Sidebar collapsible; toggle button visible; table shows reduced columns src/__tests__/integration/admin/responsive.test.tsx
layout.desktop-full Viewport 1440px width Render /admin Sidebar fixed 240px; full table columns; charts side-by-side src/__tests__/integration/admin/responsive.test.tsx

1.3 Success Criteria (Binary) — Layout & Access Control#

  • AdminShell renders with sidebar, header, and content area for ADMIN role
  • AdminSidebar displays all 9 navigation routes with correct icons and labels
  • Active nav item has aria-current="page" and visual highlight
  • AdminHeader renders breadcrumbs matching current route hierarchy
  • Notification bell displays unread count badge; dropdown opens on click
  • User menu dropdown shows admin user details and sign-out option
  • DataTable supports sorting, filtering, and pagination
  • StatCard renders label, formatted value, and color variant correctly
  • StatusBadge maps all statuses (ACTIVE, TRIAL, CHURNED, SUSPENDED, PENDING) to correct colors
  • ConfirmDialog renders title, description, cancel, and confirm buttons; fires callbacks correctly
  • FilterBar applies date range and dropdown filters; calls onFilterChange with correct shape
  • PracticeSelect renders searchable dropdown of all practices
  • JsonViewer renders structured JSON with syntax highlighting and collapsible nodes
  • /admin/* routes redirect non-ADMIN users to /dashboard with 302
  • Unauthenticated users are redirected to /login with redirect query param
  • All admin.* tRPC procedures return 403 FORBIDDEN for non-admin roles
  • All admin.* tRPC procedures return 401 UNAUTHORIZED for unauthenticated requests
  • Mobile (<768px) renders sidebar as drawer; tables as card lists; charts stacked
  • Tablet (768-1024px) renders collapsible sidebar; reduced table columns
  • Desktop (>1024px) renders fixed 240px sidebar; full table columns; charts side-by-side

1.4 Agent Context (Pre-conditions) — Layout & Access Control#

  • Required DB state: User table with at least 3 records: role: "ADMIN", role: "CLIENT", role: "EDITOR"; ClientProfile with at least 1 record
  • Required env vars: NEXTAUTH_SECRET (mocked); ADMIN_ROLE="ADMIN"
  • Required mocks: next-auth/react getServerSession mocked for role-based tests; next/navigation useRouter mocked for redirect assertions
  • Session state: Admin session with user.id, user.email, user.role: "ADMIN"; client session with user.role: "CLIENT"
  • Viewport mocks: @testing-library/react render with window.innerWidth mocked for responsive tests

1.5 Verification Commands — Layout & Access Control#

# Unit: layout components
pnpm test:unit -- src/__tests__/unit/admin/

# Integration: access control + responsive
pnpm test:integration -- src/__tests__/integration/admin/access.test.ts
pnpm test:integration -- src/__tests__/integration/admin/responsive.test.tsx

# API access control
pnpm test:integration -- src/__tests__/integration/admin/api.access.test.ts

# Full layout suite
pnpm test:unit -- src/__tests__/unit/admin/

2. KPI Dashboard (/admin) Tests#

2.1 Unit Tests — KPI Page Components#

Test Input Expected Output File
kpi-page.render AdminKPIs mock data All 6 sections render: Stat Row, Revenue Chart, Client Status, Job Health, API Costs, Activity Feed src/__tests__/unit/admin/kpi/page.test.tsx
kpi-cards.counts { totalClients: 100, activeClients: 80, trialClients: 15, churnedClients: 5 } 4 stat cards render with correct counts and labels src/__tests__/unit/admin/kpi/KPICards.test.tsx
kpi-cards.mrr { mrr: 480000, arr: 5760000 } MRR card shows "₹4,80,000"; ARR card shows "₹57,60,000" src/__tests__/unit/admin/kpi/KPICards.test.tsx
kpi-cards.citation-health { citationHealth: 87.5 } Card shows "87.5%" with green/amber color based on threshold (>=90% green, <80% red) src/__tests__/unit/admin/kpi/KPICards.test.tsx
revenue-chart.render 12 months of MRR data Recharts AreaChart renders with X-axis labels, Y-axis (INR), tooltip on hover src/__tests__/unit/admin/kpi/RevenueChart.test.tsx
client-status-chart.render { trial: 15, active: 80, churned: 5 } Recharts PieChart renders with 3 segments; legend shows labels and percentages src/__tests__/unit/admin/kpi/ClientStatusChart.test.tsx
job-health-chart.render { completed: 1200, failed: 30, running: 5 } Recharts BarChart renders with 3 bars; failed bar is red src/__tests__/unit/admin/kpi/JobHealthChart.test.tsx
api-cost-chart.render { claude: 150, openai: 50, dataforseo: 100, serpapi: 50, hyperbrowser: 50, firecrawl: 10 } Stacked bar chart renders 6 provider segments; total = ₹410 src/__tests__/unit/admin/kpi/ApiCostChart.test.tsx
activity-feed.render recentActivity: [20 AuditLogEntry items] List renders 20 items; newest first; "View all" link to /admin/reports src/__tests__/unit/admin/kpi/ActivityFeed.test.tsx
activity-feed.empty recentActivity: [] Empty state message: "No recent activity" src/__tests__/unit/admin/kpi/ActivityFeed.test.tsx

2.2 Integration Tests — KPI Data Fetching#

Test Setup Action Assertion File
kpi.data-loading admin.getKPIs tRPC query mocked with 1s delay Render /admin Skeleton loaders shown during fetch; replaced by data after resolve src/__tests__/integration/admin/kpi/data.test.tsx
kpi.data-error admin.getKPIs throws TRPCError Render /admin Error boundary or toast shows "Failed to load KPIs"; retry button visible src/__tests__/integration/admin/kpi/data.test.tsx
kpi.auto-refresh Mock admin.getKPIs returning activeJobs: 5 Wait 60s (mock timers) Query re-fetches; if activeJobs changes to 7, stat card updates src/__tests__/integration/admin/kpi/refresh.test.tsx
kpi.citation-health-calculation Citation table: 26 MATCHED, 4 FAILED out of 30 Call admin.getKPIs citationHealth = 86.67% (rounded to 86.7% or 87% depending on rounding rule) src/__tests__/integration/admin/kpi/calculation.test.ts
kpi.api-cost-aggregation AiUsage table: 100 records across 6 providers over 7 days Call admin.getKPIs apiCosts7d sums match DB aggregation exactly (to 2 decimal places) src/__tests__/integration/admin/kpi/calculation.test.ts
kpi.recent-activity-limit AuditLog table: 50 records Call admin.getKPIs recentActivity array has exactly 20 items; ordered by createdAt DESC src/__tests__/integration/admin/kpi/calculation.test.ts

2.3 Success Criteria (Binary) — KPI Dashboard#

  • /admin page renders all 6 sections: Stat Row, Revenue Chart, Client Status, Job Health, API Costs, Activity Feed
  • Stat Row shows Total Clients, Active Clients, Trial Clients, Churned Clients, MRR, ARR, Active Jobs, Failed Jobs (24h), Citation Health
  • MRR and ARR values are formatted in INR with proper comma separators (e.g., ₹4,80,000)
  • Citation Health card color-codes: >=90% green, 80-89% amber, <80% red
  • Revenue Chart renders 12-month MRR trend with correct X/Y axes and tooltips
  • Client Status Pie Chart shows Trial vs Active vs Churned with percentages
  • Job Health Bar Chart shows Completed, Failed, Running counts; failed bar is red
  • API Cost Chart shows 7-day breakdown by provider (Claude, OpenAI, DataForSEO, SerpAPI, Hyperbrowser, Firecrawl)
  • Activity Feed displays last 20 audit log entries in reverse chronological order
  • Activity Feed shows empty state when no entries exist
  • Skeleton loaders appear during data fetching; real data replaces them on success
  • Error state displays friendly message and retry button on tRPC failure
  • KPI data auto-refreshes every 60 seconds (or on window focus)
  • citationHealth is calculated as (MATCHED citations / total attempted) * 100 with correct rounding
  • apiCosts7d aggregates per-provider spend from AiUsage table over rolling 7-day window
  • recentActivity is limited to exactly 20 entries, newest first

2.4 Agent Context (Pre-conditions) — KPI Dashboard#

  • Required DB state: User (100 records), ClientProfile (80 ACTIVE, 15 TRIAL, 5 CHURNED), Subscription (with status and amount), Citation (26 MATCHED, 4 FAILED), AiUsage (100 records across 6 providers), AuditLog (50+ records), JobLog (1200 completed, 30 failed, 5 running)
  • Required env vars: ADMIN_ROLE="ADMIN"; CURRENCY_CODE="INR"
  • Required mocks: recharts components can be shallow-rendered or mocked if causing issues in JSDOM
  • tRPC mocks: admin.getKPIs and admin.getSystemHealth return deterministic mock data
  • Timer control: vi.useFakeTimers() for auto-refresh tests

2.5 Verification Commands — KPI Dashboard#

# Unit: KPI components
pnpm test:unit -- src/__tests__/unit/admin/kpi/

# Integration: KPI data + calculations
pnpm test:integration -- src/__tests__/integration/admin/kpi/

# Smoke: admin KPI page render
pnpm test:integration -- src/__tests__/integration/admin/kpi/data.test.tsx

3. Client Management Tests#

3.1 Unit Tests — Client List (/admin/clients)#

Test Input Expected Output File
client-list.render clients: [10 mock ClientProfile rows] Table renders 10 rows with Name, Slug, Type, Tier, Status, Locations, Created, Trial Ends columns src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.sort-name Click "Name" header Rows sorted A-Z; click again = Z-A src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.sort-tier Click "Tier" header Rows sorted by tier enum order (STARTER < STANDARD < PREMIUM < ENTERPRISE) src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.filter-type Select "CLINIC" from Type filter Only clinic rows visible; count updates src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.filter-status Select "TRIAL" from Status filter Only trial rows visible; count updates src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.filter-search Type "dental" in search Only rows with "dental" in name or slug visible src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.filter-date Select date range "Last 30 days" Only clients created within range visible src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.action-view Click "View" on row 1 Navigates to /admin/clients/{id} src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.action-edit Click "Edit" on row 1 Inline modal opens with ClientEditForm; pre-filled with row data src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.action-suspend Click "Suspend" on row 1 ConfirmDialog opens with "Suspend Client" title; on confirm, admin.updateClient called with status: "SUSPENDED" src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.action-delete Click "Delete" on row 1 ConfirmDialog opens with "Delete Client" title; on confirm, soft delete called (not hard delete) src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.bulk-change-tier Select 3 rows; choose "Change tier to Standard" from bulk actions admin.updateClient called 3 times with tier: "STANDARD"; toast confirmation src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.bulk-export Select 5 rows; click "Export CSV" CSV blob downloaded with correct headers and 5 data rows src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.bulk-email Select 2 rows; click "Send email" Email modal opens with pre-filled recipient list; email.send called on submit src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.empty clients: [] Empty state with "No clients found" and "Add client" button src/__tests__/unit/admin/clients/ClientListPage.test.tsx
client-list.pagination totalClients: 150, pageSize: 25 6 pages shown; page 1 has rows 1-25; "Next" advances to page 2 src/__tests__/unit/admin/clients/ClientListPage.test.tsx

3.2 Unit Tests — Client Detail (/admin/clients/[id])#

Test Input Expected Output File
client-detail.render practiceId: "prac_123", all 9 tabs data 9 tabs render: Overview, Locations, GBP, Social, Citations, Site, Jobs, Billing, Audit Log src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.overview-card practice: { name: "Dr. Smith", type: "DENTIST", tier: "PREMIUM", status: "ACTIVE", members: 3, locations: 2 } Profile card shows logo, name, type, tier, status, member count, location count src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.overview-actions Practice with status: "ACTIVE" "Edit", "Impersonate", "Suspend" buttons visible src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.locations-tab locations: [2 Location rows], gbpLocations: [1 GbpLocation] Location cards render with address; GBP status badge shows "Connected — 1 location" src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.gbp-tab gbpAccount: { isActive: true }, gbpPosts: [5], reviews: [10] Account status green; posts table with 5 rows; reviews table with 10 rows; insights charts render src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.social-tab socialAccounts: [4 SocialAccount rows] Table shows platform, name, status, follower count, last sync for each account src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.citations-tab citations: [30 Citation rows] Directory list with status badges; NAP status (MATCHED/MISMATCHED); screenshot thumbnails clickable src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.site-tab profileSections: [7 SiteSection rows], template: "dental-clean" Template name shown; section list with visibility toggles; preview iframe renders src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.jobs-tab jobs: [20 JobLog rows] Job table with ID, skill, status, duration, cost; pagination if >20 src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.billing-tab invoices: [12 Invoice rows], subscription: { status: "ACTIVE" } Invoice table with ID, amount, status, paid date; subscription card shows plan and next billing src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.audit-log-tab auditLogs: [50 AuditLog rows] Audit log table with action, actor, timestamp, IP; filterable by action type src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.not-found practiceId: "prac_nonexistent" "Client not found" error page; 404 status; link back to /admin/clients src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.suspend-action Click "Suspend" in overview ConfirmDialog opens; on confirm, admin.updateClient called; status changes to SUSPENDED src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx
client-detail.edit-action Click "Edit" in overview Inline modal opens with ClientEditForm; save calls admin.updateClient src/__tests__/unit/admin/clients/ClientDetailPage.test.tsx

3.3 Integration Tests — Client API#

Test Setup Action Assertion File
api.list-clients DB with 50 ClientProfile records Call admin.listClients Returns array of 50; each has id, name, slug, type, tier, status, locations, createdAt, trialEndsAt src/__tests__/integration/admin/clients/api.test.ts
api.list-clients-filter DB with mixed types and statuses Call admin.listClients with { type: "CLINIC", status: "ACTIVE" } Returns only clinic + active records; count matches DB query src/__tests__/integration/admin/clients/api.test.ts
api.list-clients-search DB with 10 records containing "dental" Call admin.listClients with { search: "dental" } Returns exactly 10 records; all have "dental" in name or slug src/__tests__/integration/admin/clients/api.test.ts
api.list-clients-pagination DB with 100 records Call admin.listClients with { page: 2, pageSize: 25 } Returns records 26-50; totalCount: 100, totalPages: 4 src/__tests__/integration/admin/clients/api.test.ts
api.get-client DB with practiceId: "prac_123" and related records Call admin.getClient({ id: "prac_123" }) Returns full practice object with all 9 tab relations populated src/__tests__/integration/admin/clients/api.test.ts
api.get-client-not-found No practice with id: "prac_missing" Call admin.getClient({ id: "prac_missing" }) Throws TRPCError with code NOT_FOUND src/__tests__/integration/admin/clients/api.test.ts
api.update-client Existing practice Call admin.updateClient({ id: "prac_123", tier: "PREMIUM" }) DB updated; returns updated practice; auditLog entry created src/__tests__/integration/admin/clients/api.test.ts
api.update-client-suspend Active practice Call admin.updateClient({ id: "prac_123", status: "SUSPENDED" }) DB updated to SUSPENDED; recurring jobs paused; auditLog entry created src/__tests__/integration/admin/clients/api.test.ts
api.update-client-soft-delete Active practice Call admin.updateClient({ id: "prac_123", deletedAt: <now> }) deletedAt set; data retained; login blocked; auditLog entry created src/__tests__/integration/admin/clients/api.test.ts
api.update-client-bulk-tier 3 practices with tier STARTER Call admin.updateClient 3 times with tier: "STANDARD" All 3 updated; no partial updates; transactions rollback on failure src/__tests__/integration/admin/clients/api.test.ts

3.4 Success Criteria (Binary) — Client Management#

  • /admin/clients renders data table with all 9 columns (Name, Slug, Type, Tier, Status, Locations, Created, Trial Ends, Actions)
  • All sortable columns (Name, Slug, Type, Tier, Status, Locations, Created, Trial Ends) support ascending/descending sort
  • All filterable columns (Name search, Type dropdown, Tier dropdown, Status dropdown, Created date) filter rows correctly
  • Search filter matches against name and slug fields
  • Date range filter matches createdAt and trialEndsAt fields
  • Row actions: "View" navigates to /admin/clients/{id}; "Edit" opens inline modal; "Suspend" shows confirmation; "Delete" shows confirmation (soft delete)
  • Bulk actions: "Change tier" updates all selected rows; "Export CSV" downloads correct data; "Send email" opens email modal
  • Pagination works with configurable page size; total count and page count accurate
  • Empty state renders when no clients match filters
  • /admin/clients/[id] renders 9 tabs: Overview, Locations, GBP, Social, Citations, Site, Jobs, Billing, Audit Log
  • Overview tab shows profile card with logo, name, type, tier, status, members, locations, and action buttons (Edit, Impersonate, Suspend)
  • Locations tab shows location cards with GBP connection status per location
  • GBP tab shows account status, posts table, reviews table, and insights charts
  • Social tab shows connected accounts with platform, name, status, followers, last sync
  • Citations tab shows all 30 directory submissions with status, NAP match, and screenshot
  • Site tab shows template name, section list with visibility toggles, and live preview
  • Jobs tab shows all jobs for this practice with pagination and status badges
  • Billing tab shows invoices and subscription status
  • Audit Log tab shows all actions with actor, timestamp, IP, and action type filter
  • Non-existent client ID renders 404 error page with link back to list
  • admin.listClients returns paginated list with correct filtering, sorting, and search
  • admin.getClient returns full practice with all 9 tab relations populated
  • admin.updateClient updates DB, returns updated record, and creates auditLog entry
  • Suspend action updates status to SUSPENDED and pauses recurring jobs
  • Soft delete sets deletedAt without removing data; blocks login
  • Bulk tier changes apply atomically per client; no partial updates

3.5 Agent Context (Pre-conditions) — Client Management#

  • Required DB state: ClientProfile (50+ records across all types, tiers, statuses), Location (2 per practice), GbpAccount (1 per practice), GbpLocation (1 per location), SocialAccount (0-4 per practice), Citation (30 per practice), DirectoryProfileSection (7 per practice), JobLog (20 per practice), Invoice (12 per practice), AuditLog (50 per practice), User (members per practice)
  • Required env vars: ADMIN_ROLE="ADMIN"
  • Required mocks: next/navigation useRouter and redirect mocked; next-auth/react useSession mocked for admin role
  • tRPC mocks: admin.listClients, admin.getClient, admin.updateClient return deterministic data
  • File mocks: URL.createObjectURL and Blob mocked for CSV export tests

3.6 Verification Commands — Client Management#

# Unit: client list + detail pages
pnpm test:unit -- src/__tests__/unit/admin/clients/

# Integration: client API
pnpm test:integration -- src/__tests__/integration/admin/clients/api.test.ts

# Full client management suite
pnpm test:unit -- src/__tests__/unit/admin/clients/
pnpm test:integration -- src/__tests__/integration/admin/clients/

4. Infrastructure Tests#

4.1 Unit Tests — Domain Management (/admin/directory)#

Test Input Expected Output File
domain-list.render domains: [20 Domain rows] Table shows Practice, DirectorySlug, Custom Domain, DNS Status, SSL Status, CDN Status, Actions src/__tests__/unit/admin/directory/DomainListPage.test.tsx
domain-list.dns-propagated dnsStatus: "PROPAGATED" Green checkmark badge src/__tests__/unit/admin/directory/DomainListPage.test.tsx
domain-list.dns-pending dnsStatus: "PENDING" Yellow clock badge src/__tests__/unit/admin/directory/DomainListPage.test.tsx
domain-list.dns-error dnsStatus: "ERROR" Red error badge src/__tests__/unit/admin/directory/DomainListPage.test.tsx
domain-list.action-dns-check Click "DNS Check" on row admin.checkDNS called with practiceId; status badge updates after refresh src/__tests__/unit/admin/directory/DomainListPage.test.tsx
domain-list.action-revalidate Click "Force Revalidate" on row admin.revalidateDomain called with practiceId; toast confirmation src/__tests__/unit/admin/directory/DomainListPage.test.tsx
domain-list.filter-status Select "DNS Error" from filter Only rows with dnsStatus: "ERROR" visible src/__tests__/unit/admin/directory/DomainListPage.test.tsx

4.2 Unit Tests — Website Management (/admin/directory-profiles)#

Test Input Expected Output File
website-list.render websites: [15 Website rows] Table shows Domain, Niche, Authority Score, Post Count, Linked Practice, Health, Last Published src/__tests__/unit/admin/directory-profiles/WebsiteListPage.test.tsx
website-list.health-active health: "ACTIVE" Green badge; uptime percentage shown src/__tests__/unit/admin/directory-profiles/WebsiteListPage.test.tsx
website-list.health-down health: "DOWN" Red badge; last error message shown src/__tests__/unit/admin/directory-profiles/WebsiteListPage.test.tsx
website-list.sort-authority Click "Authority Score" header Rows sorted by DA score descending src/__tests__/unit/admin/directory-profiles/WebsiteListPage.test.tsx
website-list.linked-practice linkedPractice: "Dr. Smith Dental" Practice name shown as link to /admin/clients/{id} src/__tests__/unit/admin/directory-profiles/WebsiteListPage.test.tsx
website-list.empty websites: [] Empty state with "No owned blog pages configured" src/__tests__/unit/admin/directory-profiles/WebsiteListPage.test.tsx

4.3 Integration Tests — Infrastructure API#

Test Setup Action Assertion File
api.list-domains DB with 20 Practice records with varied domain statuses Call admin.listDomains Returns array with practiceName, directorySlug, directoryProfileUrl, dnsStatus, sslStatus, cdnStatus src/__tests__/integration/admin/infrastructure/api.test.ts
api.check-dns Practice with directorySlug: "dr-smith.rankflow.in" Call admin.checkDNS({ practiceId: "prac_123" }) Performs DNS lookup; returns PROPAGATED or PENDING based on mock resolver src/__tests__/integration/admin/infrastructure/api.test.ts
api.revalidate-domain Practice with directorySlug: "dr-smith.rankflow.in" Call admin.revalidateDomain({ practiceId: "prac_123" }) Calls /api/revalidate/site with correct slug; returns { success: true } src/__tests__/integration/admin/infrastructure/api.test.ts
api.list-websites DB with 15 BlogSite records Call admin.listWebsites Returns array with domain, niche, authorityScore, postCount, linkedPractice, health, lastPublished src/__tests__/integration/admin/infrastructure/api.test.ts
api.website-health-check BlogSite with domain: "kerala-health.rankflow.in" Call admin.checkWebsiteHealth({ domain: "..." }) HTTP 200 check performed; returns { status: "ACTIVE" } or { status: "DOWN", error: "..." } src/__tests__/integration/admin/infrastructure/api.test.ts

4.4 Success Criteria (Binary) — Infrastructure#

  • /admin/directory table shows all 7 columns with correct data
  • DNS Status displays 3 states: Propagated (green), Pending (yellow), Error (red)
  • SSL Status displays 2 states: Active (green), Expired (red)
  • CDN Status displays 2 states: Cached (green), Bypass (amber)
  • "DNS Check" action performs live DNS lookup and updates status badge
  • "Force Revalidate" action triggers ISR revalidation for the domain
  • Domain list supports filtering by DNS, SSL, and CDN status
  • /admin/directory-profiles table shows all 7 columns with correct data
  • Health status shows Active (green) or Down (red) with uptime/error details
  • Authority Score sorts numerically; Post Count sorts numerically
  • Linked Practice column links to client detail page
  • Empty state renders when no blog sites exist
  • admin.listDomains returns all practice domains with correct status fields
  • admin.checkDNS performs DNS resolution and returns accurate status
  • admin.revalidateDomain calls revalidation endpoint and returns success
  • admin.listWebsites returns all owned blog pages with health and metadata
  • admin.checkWebsiteHealth performs HTTP check and returns accurate status

4.5 Agent Context (Pre-conditions) — Infrastructure#

  • Required DB state: Practice (20 records with directorySlug, directoryProfileUrl, domainStatus, sslStatus, cdnStatus), BlogSite (15 records with domain, niche, authorityScore, postCount, linkedPracticeId, health, lastPublished)
  • Required env vars: BASE_DOMAIN="rankflow.in"; API_REVALIDATE_SECRET (mocked)
  • Required mocks: DNS resolver mocked (dns.resolve or equivalent); HTTP fetch mocked for health checks; next/navigation mocked
  • tRPC mocks: admin.listDomains, admin.listWebsites, admin.checkDNS, admin.revalidateDomain return deterministic data

4.6 Verification Commands — Infrastructure#

# Unit: domain + website pages
pnpm test:unit -- src/__tests__/unit/admin/directory/
pnpm test:unit -- src/__tests__/unit/admin/directory-profiles/

# Integration: infrastructure API
pnpm test:integration -- src/__tests__/integration/admin/infrastructure/api.test.ts

5. Social Connections Tests#

5.1 Unit Tests — Social Connections (/admin/social-connections)#

Test Input Expected Output File
social-list.render accounts: [25 SocialAccount rows] Table shows Platform, Account Name, Practice, Status, Followers, Last Sync, Actions src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx
social-list.platform-icons Platforms: Facebook, Instagram, LinkedIn, Twitter Correct platform icons render (e.g., FB blue, IG gradient) src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx
social-list.status-active status: "ACTIVE" Green badge src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx
social-list.status-expired status: "EXPIRED" Red badge with "Reconnect" action src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx
social-list.status-pending status: "PENDING" Amber clock badge src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx
social-list.action-disconnect Click "Disconnect" on active account ConfirmDialog opens; on confirm, social.disconnect called; row removed or status changes to DISCONNECTED src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx
social-list.action-refresh Click "Refresh Token" on expired account social.refreshToken called; status updates to ACTIVE if success src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx
social-list.filter-platform Select "Instagram" from filter Only Instagram accounts visible src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx
social-list.filter-practice Select practice from dropdown Only accounts belonging to selected practice visible src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx
social-list.sort-followers Click "Followers" header Rows sorted by follower count descending src/__tests__/unit/admin/social/SocialConnectionsPage.test.tsx

5.2 Integration Tests — Social API#

Test Setup Action Assertion File
api.list-social DB with 25 SocialAccount records across 4 platforms Call admin.listSocialConnections Returns array with all fields; grouped by platform optionally src/__tests__/integration/admin/social/api.test.ts
api.disconnect Active SocialAccount with composioConnectionId: "conn_123" Call social.disconnect({ id: "soc_123" }) Composio API disconnect called; DB isActive set to false; auditLog entry created src/__tests__/integration/admin/social/api.test.ts
api.refresh-token SocialAccount with status: "EXPIRED", tokenExpiresAt: <past> Call social.refreshToken({ id: "soc_123" }) New token fetched from platform; DB updated; isActive set to true src/__tests__/integration/admin/social/api.test.ts
api.refresh-token-fail SocialAccount with revoked OAuth Call social.refreshToken({ id: "soc_123" }) Returns { success: false, error: "Token revoked" }; status remains EXPIRED src/__tests__/integration/admin/social/api.test.ts

5.3 Success Criteria (Binary) — Social Connections#

  • /admin/social-connections table shows all 7 columns with correct data
  • Platform icons render correctly for Facebook, Instagram, LinkedIn, Twitter
  • Status badges show Active (green), Expired (red), Pending (amber)
  • "Disconnect" action calls social.disconnect and updates status
  • "Refresh Token" action calls social.refreshToken and updates status on success
  • Expired accounts show "Reconnect" action; pending accounts show clock icon
  • Filter by platform and practice works correctly
  • Sort by follower count works numerically descending
  • admin.listSocialConnections returns all connected accounts with correct metadata
  • social.disconnect calls Composio API, updates DB, and creates audit log entry
  • social.refreshToken fetches new token, updates DB, and reactivates account
  • Token refresh failure returns clear error without crashing

5.4 Agent Context (Pre-conditions) — Social Connections#

  • Required DB state: SocialAccount (25 records across 4 platforms with varied statuses), Practice (linked to accounts), ClientProfile
  • Required env vars: COMPOSIO_API_KEY (mocked)
  • Required mocks: Composio API mocked for disconnect and refresh; next/navigation mocked
  • tRPC mocks: admin.listSocialConnections, social.disconnect, social.refreshToken return deterministic data

5.5 Verification Commands — Social Connections#

# Unit: social connections page
pnpm test:unit -- src/__tests__/unit/admin/social/

# Integration: social API
pnpm test:integration -- src/__tests__/integration/admin/social/api.test.ts

6. Job Monitor Tests#

6.1 Unit Tests — Job Monitor (/admin/jobs)#

Test Input Expected Output File
job-monitor.render jobs: [50 JobLog rows] Stat cards (Pending, Running, Completed, Failed) + data table render src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.stat-cards { pending: 12, running: 5, completed: 1200, failed: 30 } Cards show correct counts; Pending=yellow, Running=blue, Completed=green, Failed=red src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.table-columns Mixed job statuses Table shows Job ID, Skill, Practice, Status, Duration, Cost, Started, Actions src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.status-badge-pending status: "PENDING" Yellow "PENDING" badge src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.status-badge-running status: "RUNNING" Blue "RUNNING" badge src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.status-badge-completed status: "COMPLETED" Green "COMPLETED" badge src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.status-badge-failed status: "FAILED" Red "FAILED" badge src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.action-retry Click "Retry" on failed job row admin.retryJob called with jobId; toast shows "Job queued for retry" src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.action-view-logs Click "View logs" on failed job JsonViewer modal opens with job payload, error stack, and attempt history src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.filter-status Select "FAILED" from status multi-select Only failed jobs visible; stat cards still show totals src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.filter-practice Select practice from dropdown Only jobs for selected practice visible src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.filter-skill Select skill from dropdown Only jobs for selected skill visible src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.filter-date-range Select "Last 24 hours" Only jobs with startedAt within range visible src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.auto-refresh jobs data changes after 10s Table updates automatically every 10 seconds; new counts reflected in stat cards src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.duration-format duration: 12.3 Displays "12.3s" src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.cost-format cost: 0.045 Displays "$0.05" (rounded to 2 decimals) src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.empty jobs: [] Empty state with "No jobs in queue" src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx
job-monitor.pagination totalJobs: 200 Correct page count; page size selector works src/__tests__/unit/admin/jobs/JobMonitorPage.test.tsx

6.2 Integration Tests — Job Monitor API#

Test Setup Action Assertion File
api.list-jobs BullMQ queues with 200 jobs across all states Call admin.listJobs Returns array with id, skill, practiceName, status, duration, cost, startedAt src/__tests__/integration/admin/jobs/api.test.ts
api.list-jobs-filter-status 50 pending, 30 running, 100 completed, 20 failed Call admin.listJobs({ status: ["FAILED"] }) Returns exactly 20 jobs; all have status: "FAILED" src/__tests__/integration/admin/jobs/api.test.ts
api.list-jobs-filter-practice Jobs from 10 different practices Call admin.listJobs({ practiceId: "prac_123" }) Returns only jobs for that practice src/__tests__/integration/admin/jobs/api.test.ts
api.list-jobs-filter-skill Jobs from 15 different skills Call admin.listJobs({ skill: "gbp-post-publish" }) Returns only jobs for that skill src/__tests__/integration/admin/jobs/api.test.ts
api.list-jobs-filter-date Jobs from last 7 days Call admin.listJobs({ dateFrom: "2026-06-06", dateTo: "2026-06-13" }) Returns only jobs within date range src/__tests__/integration/admin/jobs/api.test.ts
api.retry-job Failed job in DLQ with id: "job_456" Call admin.retryJob({ id: "job_456" }) Job re-added to original queue; removed from DLQ; returns { success: true, newJobId: "..." } src/__tests__/integration/admin/jobs/api.test.ts
api.retry-job-invalid Job with status: "COMPLETED" Call admin.retryJob({ id: "job_789" }) Returns { success: false, error: "Job is not in failed state" } src/__tests__/integration/admin/jobs/api.test.ts
api.retry-job-missing jobId: "job_nonexistent" Call admin.retryJob({ id: "job_nonexistent" }) Returns { success: false, error: "Job not found" } src/__tests__/integration/admin/jobs/api.test.ts
api.job-stats Queue with 12 pending, 5 running, 1200 completed, 30 failed Call admin.getJobStats Returns { pending: 12, running: 5, completed: 1200, failed: 30 } src/__tests__/integration/admin/jobs/api.test.ts
api.job-logs Job with 3 attempts Call admin.getJobLogs({ id: "job_123" }) Returns array of 3 log entries with timestamps, error messages, and stack traces src/__tests__/integration/admin/jobs/api.test.ts

6.3 Success Criteria (Binary) — Job Monitor#

  • /admin/jobs renders 4 stat cards (Pending, Running, Completed, Failed) with correct colors
  • Job table shows all 8 columns (Job ID, Skill, Practice, Status, Duration, Cost, Started, Actions)
  • Status badges render correctly for PENDING, RUNNING, COMPLETED, FAILED
  • "Retry" action on failed job calls admin.retryJob and shows toast confirmation
  • "View logs" action opens modal with JsonViewer showing payload, error, and attempt history
  • Status multi-select filter shows only jobs with selected statuses
  • Practice dropdown filter shows only jobs for selected practice
  • Skill dropdown filter shows only jobs for selected skill
  • Date range filter shows only jobs within selected range
  • Table auto-refreshes every 10 seconds with updated queue state
  • Duration formatted as seconds with 1 decimal (e.g., "12.3s")
  • Cost formatted as USD with 2 decimals (e.g., "$0.05")
  • Pagination works with configurable page size
  • Empty state renders when no jobs match filters
  • admin.listJobs returns paginated, filtered job list with all metadata
  • admin.retryJob re-queues failed job from DLQ to original queue
  • admin.retryJob rejects non-failed jobs with clear error message
  • admin.retryJob returns clear error for non-existent job IDs
  • admin.getJobStats returns accurate queue depth counts per status
  • admin.getJobLogs returns full attempt history with error details

6.4 Agent Context (Pre-conditions) — Job Monitor#

  • Required DB state: JobLog (200+ records across all statuses), Practice (linked to jobs), DLQ with 20 failed jobs
  • Required env vars: REDIS_URL for ioredis-mock; BULLMQ_PREFIX (optional)
  • Required mocks: BullMQ queue state mocked with known job counts; next/navigation mocked
  • Mock Redis state: Fresh ioredis-mock with populated queues; flushall between suites
  • tRPC mocks: admin.listJobs, admin.retryJob, admin.getJobStats, admin.getJobLogs return deterministic data
  • Timer control: vi.useFakeTimers() for auto-refresh tests

6.5 Verification Commands — Job Monitor#

# Unit: job monitor page
pnpm test:unit -- src/__tests__/unit/admin/jobs/

# Integration: job API + retry
pnpm test:integration -- src/__tests__/integration/admin/jobs/api.test.ts

# Full job monitor suite
pnpm test:unit -- src/__tests__/unit/admin/jobs/
pnpm test:integration -- src/__tests__/integration/admin/jobs/

7. Content Review Tests#

7.1 Unit Tests — Content Review (/admin/content)#

Test Input Expected Output File
content-review.render contentPieces: [40 ContentPiece rows] Split view renders: list left, preview right; Prompt Management tab visible src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.list-columns Mixed content types List shows Title/Type, Practice, Status, AI Provider, Cost, Created src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.status-draft status: "DRAFT" Gray badge src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.status-pending status: "PENDING_REVIEW" Yellow badge with countdown timer src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.status-approved status: "APPROVED" Green badge src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.status-published status: "PUBLISHED" Blue badge src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.status-rejected status: "REJECTED" Red badge src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.preview-render Select content piece with html: "<h1>Title</h1><p>Body</p>" Preview panel renders HTML with dangerouslySetInnerHTML; SEO score and readability score visible src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.action-approve Select PENDING_REVIEW piece; click "Approve" admin.approveContent called with id; status changes to APPROVED; toast confirmation src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.action-reject Select PENDING_REVIEW piece; click "Reject" admin.rejectContent called with id; status changes to REJECTED; toast confirmation src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.action-edit Select DRAFT piece; click "Edit" Inline editor opens with content; save calls admin.updateContent src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.prompt-tab Click "Prompt Management" tab Prompt template list renders; system prompt and user prompt templates visible src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.prompt-edit Click "Edit" on system prompt Inline editor opens; save calls admin.updatePrompt with new template src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.prompt-ab Select prompt with A/B variants Performance scores (click-through, conversion) shown per variant; winner highlighted src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.filter-status Select "PENDING_REVIEW" from filter Only pending review pieces visible src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.filter-practice Select practice from dropdown Only content for selected practice visible src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.filter-provider Select "Claude" from AI Provider filter Only Claude-generated content visible src/__tests__/unit/admin/content/ContentReviewPage.test.tsx
content-review.empty contentPieces: [] Empty state with "No content awaiting review" src/__tests__/unit/admin/content/ContentReviewPage.test.tsx

7.2 Integration Tests — Content API#

Test Setup Action Assertion File
api.list-content DB with 40 ContentPiece records across all statuses Call admin.listContent Returns array with id, title, type, practiceName, status, aiProvider, cost, createdAt src/__tests__/integration/admin/content/api.test.ts
api.list-content-filter Mixed statuses, providers, practices Call admin.listContent with { status: "PENDING_REVIEW", aiProvider: "Claude" } Returns only matching records; count accurate src/__tests__/integration/admin/content/api.test.ts
api.approve-content ContentPiece with status: "PENDING_REVIEW", autoPublishAt: <future> Call admin.approveContent({ id: "content_123" }) DB status updated to "APPROVED"; publish scheduled immediately; auditLog entry created src/__tests__/integration/admin/content/api.test.ts
api.reject-content ContentPiece with status: "PENDING_REVIEW" Call admin.rejectContent({ id: "content_123" }) DB status updated to "REJECTED"; auditLog entry created; triggers regeneration if configured src/__tests__/integration/admin/content/api.test.ts
api.update-prompt Existing prompt template Call admin.updatePrompt({ id: "prompt_1", systemPrompt: "New system prompt..." }) DB updated; version incremented; auditLog entry created; profile variant test flag preserved src/__tests__/integration/admin/content/api.test.ts
api.content-preview ContentPiece with html and seoScore: 85, readabilityScore: 72 Call content.getPreview({ id: "content_123" }) Returns rendered HTML, SEO score, readability score, and suggestion list src/__tests__/integration/admin/content/api.test.ts

7.3 Success Criteria (Binary) — Content Review#

  • /admin/content renders split view: list left, preview right; Prompt Management tab available
  • Content list shows all 6 columns (Title/Type, Practice, Status, AI Provider, Cost, Created)
  • Status badges render correctly for DRAFT, PENDING_REVIEW, APPROVED, PUBLISHED, REJECTED, FAILED
  • PENDING_REVIEW badge shows 24-hour auto-publish countdown timer
  • Preview panel renders HTML content safely; SEO score and readability score visible
  • "Approve" action calls admin.approveContent; status changes to APPROVED; toast confirms
  • "Reject" action calls admin.rejectContent; status changes to REJECTED; toast confirms
  • "Edit" action opens inline editor; save calls admin.updateContent
  • Prompt Management tab lists all prompt templates with system and user prompts
  • Prompt edit opens inline editor; save increments version and creates audit log entry
  • profile variant test variants show performance scores; winner highlighted
  • Filter by status, practice, and AI provider works correctly
  • Empty state renders when no content pieces match filters
  • admin.listContent returns paginated, filtered content list with all metadata
  • admin.approveContent updates status, schedules publish, and creates audit log entry
  • admin.rejectContent updates status, creates audit log, and optionally triggers regeneration
  • admin.updatePrompt updates template, increments version, and preserves profile variant test config
  • content.getPreview returns rendered HTML with SEO and readability scores

7.4 Agent Context (Pre-conditions) — Content Review#

  • Required DB state: ContentPiece (40 records across all statuses, types, providers), Practice (linked to content), PromptTemplate (5 templates with A/B variants), AuditLog
  • Required env vars: AI_MODELS config (mocked)
  • Required mocks: next/navigation mocked; HTML sanitizer mocked if needed
  • tRPC mocks: admin.listContent, admin.approveContent, admin.rejectContent, admin.updatePrompt, content.getPreview return deterministic data

7.5 Verification Commands — Content Review#

# Unit: content review page
pnpm test:unit -- src/__tests__/unit/admin/content/

# Integration: content API
pnpm test:integration -- src/__tests__/integration/admin/content/api.test.ts

8. Billing Tests#

8.1 Unit Tests — Billing (/admin/billing)#

Test Input Expected Output File
billing.render revenueData, subscriptions, invoices, failedCharges 4 revenue cards + 3 tables render src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx
billing.revenue-cards { mrr: 480000, arr: 5760000, activeSubscriptions: 80, failedPayments: 3 } Cards show MRR "₹4,80,000", ARR "₹57,60,000", 80 active, 3 failed; failed card is red src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx
billing.subscriptions-table subscriptions: [80 Subscription rows] Table shows Practice, Tier, Status, Next Billing, Amount; pagination if >25 src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx
billing.invoices-table invoices: [200 Invoice rows] Table shows Invoice ID, Practice, Amount, Status, Paid Date; sortable by date src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx
billing.failed-charges-table failedCharges: [5 FailedCharge rows] Table shows Practice, Amount, Failure Reason, Retry Count; red status badges src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx
billing.filter-tier Select "PREMIUM" from tier filter Only premium subscriptions visible src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx
billing.filter-status Select "Past Due" from status filter Only past due subscriptions visible src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx
billing.search-practice Type "dental" in search Only subscriptions/invoices for matching practices visible src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx
billing.export-revenue Click "Export Revenue CSV" CSV blob downloaded with revenue by month data src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx
billing.empty-invoices invoices: [] Empty state "No invoices found" src/__tests__/unit/admin/billing/BillingOverviewPage.test.tsx

8.2 Integration Tests — Billing API#

Test Setup Action Assertion File
api.get-revenue DB with 80 active subscriptions across 4 tiers Call admin.getRevenue Returns { mrr, arr, activeSubscriptions, failedPayments, revenueByMonth: [{ month, amount }] } src/__tests__/integration/admin/billing/api.test.ts
api.mrr-calculation Subscriptions: 20 Starter @ ₹4,000, 40 Standard @ ₹8,000, 15 Premium @ ₹12,000, 5 Enterprise @ ₹20,000 Call admin.getRevenue MRR = (20×4000 + 40×8000 + 15×12000 + 5×20000) = ₹6,80,000; ARR = MRR × 12 src/__tests__/integration/admin/billing/api.test.ts
api.list-invoices DB with 200 Invoice records Call billing.listInvoices Returns paginated array with id, practiceName, amount, status, paidDate, pdfUrl src/__tests__/integration/admin/billing/api.test.ts
api.list-invoices-filter Invoices from 10 practices Call billing.listInvoices({ practiceId: "prac_123" }) Returns only invoices for that practice src/__tests__/integration/admin/billing/api.test.ts
api.failed-charges DB with 5 failed payments in last 7 days Call admin.getFailedCharges Returns array with practiceName, amount, failureReason, retryCount, lastRetryAt src/__tests__/integration/admin/billing/api.test.ts
api.subscription-detail Subscription with status: "trialing", trialEndsAt: <future> Call billing.getSubscription({ practiceId: "prac_123" }) Returns full subscription with plan, status, next billing, payment method, and trial info src/__tests__/integration/admin/billing/api.test.ts

8.3 Success Criteria (Binary) — Billing#

  • /admin/billing renders 4 revenue cards (MRR, ARR, Active Subscriptions, Failed Payments)
  • MRR and ARR formatted in INR with comma separators
  • Failed Payments card shows red color when > 0
  • Subscriptions table shows 5 columns (Practice, Tier, Status, Next Billing, Amount)
  • Invoices table shows 5 columns (Invoice ID, Practice, Amount, Status, Paid Date)
  • Failed Charges table shows 4 columns (Practice, Amount, Failure Reason, Retry Count)
  • Filter by tier, status, and practice search works correctly
  • Revenue export CSV downloads correct monthly revenue data
  • Empty state renders when no invoices or failed charges exist
  • admin.getRevenue calculates MRR as sum of all active subscription amounts
  • admin.getRevenue calculates ARR as MRR × 12
  • admin.getRevenue returns accurate activeSubscriptions count by tier
  • admin.getRevenue returns accurate failedPayments count for last 7 days
  • billing.listInvoices returns paginated, filtered invoice list with all metadata
  • admin.getFailedCharges returns all failed payments in last 7 days with retry info
  • billing.getSubscription returns full subscription details including trial status

8.4 Agent Context (Pre-conditions) — Billing#

  • Required DB state: Subscription (80 active across 4 tiers), Invoice (200 records), Billing (with failed charges), ClientProfile (linked to subscriptions), PaymentMethod (linked to billing)
  • Required env vars: CURRENCY_CODE="INR"; STRIPE_WEBHOOK_SECRET / RAZORPAY_WEBHOOK_SECRET (mocked)
  • Required mocks: next/navigation mocked; CSV export utilities mocked
  • tRPC mocks: admin.getRevenue, billing.listInvoices, admin.getFailedCharges, billing.getSubscription return deterministic data

8.5 Verification Commands — Billing#

# Unit: billing page
pnpm test:unit -- src/__tests__/unit/admin/billing/

# Integration: billing API + MRR calculation
pnpm test:integration -- src/__tests__/integration/admin/billing/api.test.ts

9. System Reports Tests#

9.1 Unit Tests — System Reports (/admin/reports)#

Test Input Expected Output File
reports.render reports data loaded 6 report cards render with title, period, and export buttons src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.revenue-by-month Click "Revenue by Month" card Table shows month, MRR, new clients, churned clients, net revenue; CSV export available src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.client-churn Click "Client Churn" card Table shows month, starting clients, churned, churn rate %, reason breakdown; CSV export available src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.citation-success Click "Citation Success Rate" card Table shows week, submitted count, failed count, success rate %, top failing directories; CSV export available src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.api-cost-breakdown Click "API Cost Breakdown" card Table shows day, provider, requests, cost; stacked bar chart by provider; CSV export available src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.content-volume Click "Content Generation Volume" card Table shows week, articles generated, GBP posts, social posts, total cost; CSV export available src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.support-tickets Click "Support Ticket Summary" card Table shows month, total tickets, resolved, avg resolution time, categories; CSV export available src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.export-csv Click "Export CSV" on any report CSV blob downloaded with correct headers and all data rows src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.export-pdf Click "Export PDF" on "Revenue by Month" PDF blob downloaded with branded report layout src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.date-range-filter Select "Last 3 months" from global filter All report cards update to show data for selected date range src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx
reports.empty No data for selected range Empty state with "No data for selected period" src/__tests__/unit/admin/reports/SystemReportsPage.test.tsx

9.2 Integration Tests — Reports API#

Test Setup Action Assertion File
api.revenue-by-month DB with 12 months of subscription data Call admin.getRevenue({ period: "monthly", months: 12 }) Returns 12 rows with month, mrr, newClients, churnedClients, netRevenue src/__tests__/integration/admin/reports/api.test.ts
api.churn-calculation DB: 100 clients at start of month, 5 churned during month Call admin.getChurnReport({ month: "2026-05" }) churnRate = 5.0%; startingClients = 100; churned = 5 src/__tests__/integration/admin/reports/api.test.ts
api.citation-success-rate DB: 30 citations attempted, 24 submitted, 6 failed Call admin.getCitationSuccessReport({ week: "2026-W23" }) successRate = 80.0%; submitted = 24; failed = 6; failing directories listed src/__tests__/integration/admin/reports/api.test.ts
api.api-cost-daily AiUsage table: 50 records across 6 providers over 7 days Call admin.getApiCostReport({ period: "daily", days: 7 }) Returns 7 rows with daily totals per provider; grand total matches sum of all records src/__tests__/integration/admin/reports/api.test.ts
api.content-volume ContentPiece table: 100 articles, 50 GBP posts, 40 social posts Call admin.getContentVolumeReport({ week: "2026-W23" }) articles = 100; gbpPosts = 50; socialPosts = 40; totalCost = sum of AiUsage for content generation src/__tests__/integration/admin/reports/api.test.ts
api.support-tickets SupportTicket table: 20 tickets in month Call admin.getSupportTicketReport({ month: "2026-05" }) total = 20; resolved = 18; avgResolutionTime = calculated; categories breakdown src/__tests__/integration/admin/reports/api.test.ts
api.gross-margin MRR = ₹6,80,000; COGS = ₹50,000 Call admin.getGrossMarginReport({ month: "2026-05" }) grossMargin = ((6,80,000 - 50,000) / 6,80,000) × 100 = 92.65% src/__tests__/integration/admin/reports/api.test.ts

9.3 Success Criteria (Binary) — System Reports#

  • /admin/reports renders 6 report cards: Revenue by Month, Client Churn, Citation Success Rate, API Cost Breakdown, Content Generation Volume, Support Ticket Summary
  • Each report card shows period label and export buttons (CSV, PDF where applicable)
  • Revenue by Month table shows month, MRR, new clients, churned clients, net revenue
  • Client Churn table shows month, starting clients, churned, churn rate %, reason breakdown
  • Citation Success Rate table shows week, submitted, failed, success rate %, top failing directories
  • API Cost Breakdown table shows day, provider, requests, cost; stacked bar chart renders
  • Content Generation Volume table shows week, articles, GBP posts, social posts, total cost
  • Support Ticket Summary table shows month, total, resolved, avg resolution time, categories
  • CSV export downloads correct data with proper headers
  • PDF export downloads branded report for Revenue by Month
  • Global date range filter updates all report cards simultaneously
  • Empty state renders when no data for selected period
  • admin.getRevenue returns monthly revenue data with accurate MRR, new clients, churned clients
  • Churn rate calculated as (churned / startingClients) * 100 with correct rounding
  • Citation success rate calculated as (submitted / attempted) * 100 with correct rounding
  • API cost breakdown aggregates per provider per day with correct totals
  • Content volume report counts all content types and sums generation costs
  • Gross margin calculated as ((MRR - COGS) / MRR) * 100 with correct rounding

9.4 Agent Context (Pre-conditions) — System Reports#

  • Required DB state: Subscription (12 months), ClientProfile (with churnedAt dates), Citation (with weekly batch data), AiUsage (daily records), ContentPiece (weekly counts), SupportTicket (monthly records), Invoice (monthly)
  • Required env vars: CURRENCY_CODE="INR"
  • Required mocks: CSV/PDF generation utilities mocked; next/navigation mocked
  • tRPC mocks: admin.getRevenue, admin.getChurnReport, admin.getCitationSuccessReport, admin.getApiCostReport, admin.getContentVolumeReport, admin.getSupportTicketReport return deterministic data

9.5 Verification Commands — System Reports#

# Unit: reports page
pnpm test:unit -- src/__tests__/unit/admin/reports/

# Integration: reports API + calculations
pnpm test:integration -- src/__tests__/integration/admin/reports/api.test.ts

10. Impersonation Feature Tests#

10.1 Unit Tests — Impersonation#

Test Input Expected Output File
impersonate.button-render Admin viewing client detail page "Impersonate" button visible in overview card actions src/__tests__/unit/admin/impersonation/ImpersonateButton.test.tsx
impersonate.button-hidden Non-admin viewing any page "Impersonate" button not rendered src/__tests__/unit/admin/impersonation/ImpersonateButton.test.tsx
impersonate.modal-confirm Click "Impersonate" button Modal opens with warning: "You are about to view this client's dashboard. All actions will be logged." src/__tests__/unit/admin/impersonation/ImpersonateButton.test.tsx
impersonate.badge-render Admin is impersonating practiceId: "prac_123" Red banner at top: "Impersonating Dr. Smith Dental — Exit Impersonation" src/__tests__/unit/admin/impersonation/ImpersonationBanner.test.tsx
impersonate.badge-exit Click "Exit Impersonation" in banner Admin redirected back to /admin/clients/prac_123; x-practice-id header cleared src/__tests__/unit/admin/impersonation/ImpersonationBanner.test.tsx
impersonate.session-indicator session.impersonating: true, session.impersonatedPracticeId: "prac_123" Session object includes impersonation flags; UI reflects impersonation state src/__tests__/unit/admin/impersonation/ImpersonationBanner.test.tsx

10.2 Integration Tests — Impersonation API & Flow#

Test Setup Action Assertion File
impersonate.start Admin session; practiceId: "prac_123" Call admin.impersonate({ practiceId: "prac_123" }) Session updated with impersonating: true, impersonatedPracticeId: "prac_123"; x-practice-id header set to "prac_123" src/__tests__/integration/admin/impersonation/api.test.ts
impersonate.procedure-access Admin impersonating prac_123 Call practice.get({ id: "prac_123" }) via practiceProcedure Returns practice data successfully (admin temporarily has practiceProcedure access) src/__tests__/integration/admin/impersonation/api.test.ts
impersonate.procedure-blocked-other Admin impersonating prac_123 Call practice.get({ id: "prac_456" }) via practiceProcedure Returns TRPCError with code FORBIDDEN (cannot access other practice while impersonating) src/__tests__/integration/admin/impersonation/api.test.ts
impersonate.audit-log Admin impersonating prac_123 Call admin.impersonate({ practiceId: "prac_123" }) AuditLog entry created with action: "IMPERSONATE_START", actorId: <admin_id>, targetPracticeId: "prac_123", ipAddress, timestamp src/__tests__/integration/admin/impersonation/api.test.ts
impersonate.audit-log-end Admin exits impersonation Call admin.endImpersonate() AuditLog entry created with action: "IMPERSONATE_END", actorId: <admin_id>, targetPracticeId: "prac_123" src/__tests__/integration/admin/impersonation/api.test.ts
impersonate.dashboard-view Admin impersonating prac_123 Navigate to /dashboard Client dashboard renders with prac_123 data; admin sidebar replaced by client sidebar; impersonation banner visible src/__tests__/integration/admin/impersonation/flow.test.tsx
impersonate.action-audit Admin impersonating prac_123; clicks "Approve Content" on client dashboard Action executes with practiceId: "prac_123" AuditLog entry created with action: "CONTENT_APPROVED", actorId: <admin_id>, practiceId: "prac_123", impersonating: true src/__tests__/integration/admin/impersonation/flow.test.tsx
impersonate.client-blocked Client session Call admin.impersonate({ practiceId: "prac_123" }) Returns TRPCError with code FORBIDDEN (only ADMIN can impersonate) src/__tests__/integration/admin/impersonation/api.test.ts
impersonate.nonexistent-practice Admin session; practiceId: "prac_missing" Call admin.impersonate({ practiceId: "prac_missing" }) Returns TRPCError with code NOT_FOUND src/__tests__/integration/admin/impersonation/api.test.ts
impersonate.suspended-practice Admin session; practiceId: "prac_suspended" with status: "SUSPENDED" Call admin.impersonate({ practiceId: "prac_suspended" }) Returns TRPCError with code BAD_REQUEST and message "Cannot impersonate suspended client" src/__tests__/integration/admin/impersonation/api.test.ts
impersonate.session-persistence Admin impersonating; refreshes page Reload /dashboard Impersonation state preserved in session; x-practice-id header still set; banner still visible src/__tests__/integration/admin/impersonation/flow.test.tsx
impersonate.timeout Admin impersonating for > 2 hours Wait 2 hours (mock timers) Impersonation auto-expires; session cleared; redirected to /admin/clients/{id} src/__tests__/integration/admin/impersonation/flow.test.tsx

10.3 Success Criteria (Binary) — Impersonation#

  • "Impersonate" button visible on client detail page for ADMIN role only
  • Clicking "Impersonate" opens confirmation modal with warning message
  • Confirming impersonation updates session with impersonating: true and impersonatedPracticeId
  • x-practice-id header set to impersonated practice ID on all subsequent requests
  • Admin gains temporary practiceProcedure access for the impersonated practice only
  • Access to other practices is blocked during impersonation (403 FORBIDDEN)
  • Client dashboard renders correctly when accessed via impersonation
  • Client sidebar replaces admin sidebar during impersonation
  • Red impersonation banner shows practice name and "Exit Impersonation" button at top of page
  • Clicking "Exit Impersonation" clears session flags, removes header, redirects to admin client detail
  • AuditLog entry created with IMPERSONATE_START on impersonation begin
  • AuditLog entry created with IMPERSONATE_END on impersonation end
  • All actions performed during impersonation are logged with impersonating: true flag
  • Non-admin users cannot call admin.impersonate (403 FORBIDDEN)
  • Impersonating non-existent practice returns 404 NOT FOUND
  • Impersonating suspended practice returns 400 BAD REQUEST with clear message
  • Impersonation state persists across page reloads (session-backed)
  • Impersonation auto-expires after 2 hours of inactivity; session cleared

10.4 Agent Context (Pre-conditions) — Impersonation#

  • Required DB state: User (1 admin, 1 client), ClientProfile (2 practices: 1 active, 1 suspended), AuditLog (empty or seeded), ContentPiece (pending review for impersonated practice)
  • Required env vars: IMPERSONATION_TIMEOUT_MINUTES=120; ADMIN_ROLE="ADMIN"
  • Required mocks: next-auth/react getServerSession mocked to return admin session with impersonation flags; next/navigation mocked for redirect assertions
  • Session state: Admin session must support impersonating and impersonatedPracticeId fields
  • tRPC mocks: admin.impersonate, admin.endImpersonate, practice.get return deterministic data based on impersonation state
  • Timer control: vi.useFakeTimers() for timeout tests

10.5 Verification Commands — Impersonation#

# Unit: impersonation components
pnpm test:unit -- src/__tests__/unit/admin/impersonation/

# Integration: impersonation API + flow
pnpm test:integration -- src/__tests__/integration/admin/impersonation/api.test.ts
pnpm test:integration -- src/__tests__/integration/admin/impersonation/flow.test.tsx

# Full impersonation suite
pnpm test:unit -- src/__tests__/unit/admin/impersonation/
pnpm test:integration -- src/__tests__/integration/admin/impersonation/

11. KPI Calculation Tests#

11.1 Unit Tests — KPI Calculation Logic#

Test Input Expected Output File
kpi.mrr-simple Subscriptions: [Starter ₹4,000, Standard ₹8,000, Premium ₹12,000] MRR = ₹24,000 src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.mrr-trial-excluded Subscriptions: [Active ₹8,000, Trial ₹4,000, Churned ₹12,000] MRR = ₹8,000 (trial and churned excluded) src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.mrr-paused-excluded Subscriptions: [Active ₹8,000, Paused ₹8,000] MRR = ₹8,000 (paused excluded) src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.arr-calculation MRR = ₹4,80,000 ARR = ₹57,60,000 src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.citation-health-perfect 30 citations, 30 MATCHED 100.0% src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.citation-health-partial 30 citations, 24 MATCHED, 4 MISMATCHED, 2 FAILED 80.0% (MATCHED / total attempted) src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.citation-health-zero 0 citations attempted 0.0% (no division by zero) src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.churn-rate 100 clients start of month, 5 churned 5.0% src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.churn-rate-zero 100 clients start, 0 churned 0.0% src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.api-cost-aggregation AiUsage: [{ provider: "claude", cost: 150 }, { provider: "claude", cost: 25 }, { provider: "openai", cost: 50 }] { claude: 175, openai: 50 } src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.gross-margin MRR = ₹6,80,000, COGS = ₹50,000 92.65% (rounded to 2 decimals) src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.gross-margin-zero-mrr MRR = 0, COGS = 0 0.0% (no division by zero) src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.nap-consistency 30 citations, 28 MATCHED, 2 MISMATCHED 93.33% (MATCHED / total live) src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.gbp-post-success 15 scheduled, 14 PUBLISHED, 1 FAILED 93.33% src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.social-post-success 20 scheduled, 18 PUBLISHED, 2 FAILED 90.0% src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.token-refresh-success 50 tokens, 49 refreshed, 1 failed 98.0% src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.email-delivery-rate 1000 sent, 950 delivered, 50 bounced 95.0% src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.failed-jobs-24h Jobs: 100 completed today, 5 failed today, 3 failed yesterday failedJobs24h = 5 src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.active-jobs Jobs: 5 RUNNING, 12 PENDING, 100 COMPLETED, 20 FAILED activeJobs = 17 (RUNNING + PENDING) src/__tests__/unit/admin/kpi/calculations.test.ts
kpi.customer-engagement 80 clients, 60 logged in >=2 times this week 75.0% engagement rate src/__tests__/unit/admin/kpi/calculations.test.ts

11.2 Integration Tests — KPI Aggregation from DB#

Test Setup Action Assertion File
kpi.db-mrr-aggregation Subscription table: 80 active across 4 tiers Query SELECT SUM(amount) FROM Subscription WHERE status = 'active' Result equals calculated MRR from admin.getKPIs src/__tests__/integration/admin/kpi/db-aggregation.test.ts
kpi.db-citation-health Citation table: 30 records, 26 MATCHED, 4 FAILED Query SELECT COUNT(*) FROM Citation WHERE matchStatus = 'MATCHED' citationHealth from API = (26 / 30) * 100 = 86.67% src/__tests__/integration/admin/kpi/db-aggregation.test.ts
kpi.db-api-cost-7d AiUsage table: 100 records over 7 days Query SELECT provider, SUM(cost) FROM AiUsage WHERE createdAt >= now() - interval '7 days' GROUP BY provider Sums match apiCosts7d from API exactly src/__tests__/integration/admin/kpi/db-aggregation.test.ts
kpi.db-failed-jobs-24h JobLog table: 5 failed in last 24h, 3 failed 48h ago Query SELECT COUNT(*) FROM JobLog WHERE status = 'FAILED' AND createdAt >= now() - interval '24 hours' Count matches failedJobs24h from API src/__tests__/integration/admin/kpi/db-aggregation.test.ts
kpi.db-recent-activity AuditLog table: 50 records Query SELECT * FROM AuditLog ORDER BY createdAt DESC LIMIT 20 Result matches recentActivity from API exactly src/__tests__/integration/admin/kpi/db-aggregation.test.ts
kpi.db-churn-count ClientProfile table: 5 with status: "CHURNED", churnedAt in current month Query SELECT COUNT(*) FROM ClientProfile WHERE status = 'CHURNED' AND churnedAt >= date_trunc('month', now()) Count matches churnedClients from API src/__tests__/integration/admin/kpi/db-aggregation.test.ts

11.3 Success Criteria (Binary) — KPI Calculation#

  • MRR calculated as sum of all active subscription amounts; trial, churned, and paused excluded
  • ARR calculated as MRR × 12
  • Citation health calculated as (MATCHED citations / total attempted citations) * 100 with no division by zero
  • Churn rate calculated as (churned clients / starting clients) * 100 with no division by zero
  • API cost 7-day aggregation sums per provider correctly from AiUsage table
  • Gross margin calculated as ((MRR - COGS) / MRR) * 100 with no division by zero
  • NAP consistency calculated as (MATCHED live citations / total live citations) * 100
  • GBP post success rate calculated as (PUBLISHED / total scheduled) * 100
  • Social post success rate calculated as (PUBLISHED / total scheduled) * 100
  • Token refresh success rate calculated as (successful refreshes / total attempts) * 100
  • Email delivery rate calculated as (DELIVERED / total sent) * 100
  • Failed jobs 24h count only includes jobs failed in last 24 hours
  • Active jobs count includes both RUNNING and PENDING statuses
  • Customer engagement rate calculated as (clients with >=2 logins this week / total active clients) * 100
  • All KPI DB aggregations match direct SQL query results exactly
  • All percentage calculations handle zero denominators gracefully (return 0.0%)

11.4 Agent Context (Pre-conditions) — KPI Calculation#

  • Required DB state: Subscription (80 active, 15 trial, 5 churned), ClientProfile (with churnedAt dates), Citation (30 records with varied matchStatus), AiUsage (100 records over 7 days across 6 providers), JobLog (mixed statuses and timestamps), AuditLog (50 records), EmailLog (1000 records with varied statuses), OauthToken (50 records with refresh history)
  • Required env vars: CURRENCY_CODE="INR"
  • Required mocks: Prisma aggregation queries mocked if using mock DB; direct SQL assertions via test DB
  • Calculation baseline: Pre-computed expected values for all KPIs to assert against

11.5 Verification Commands — KPI Calculation#

# Unit: KPI calculation logic
pnpm test:unit -- src/__tests__/unit/admin/kpi/calculations.test.ts

# Integration: DB aggregation accuracy
pnpm test:integration -- src/__tests__/integration/admin/kpi/db-aggregation.test.ts

# Full KPI suite
pnpm test:unit -- src/__tests__/unit/admin/kpi/
pnpm test:integration -- src/__tests__/integration/admin/kpi/

12. Job Monitoring & Alert Threshold Tests#

12.1 Unit Tests — Alert Threshold Logic#

Test Input Expected Output File
alert.queue-depth-warning queueDepth: 150 alertTriggered: true, severity: "WARNING", alertType: "QUEUE_DEPTH_HIGH" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.queue-depth-normal queueDepth: 50 alertTriggered: false src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.failure-rate-warning failed: 6, total: 100, window: "1h" alertTriggered: true, severity: "WARNING", alertType: "FAILURE_RATE_HIGH" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.failure-rate-normal failed: 1, total: 100, window: "1h" alertTriggered: false src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.api-cost-warning dailyCost: 55, practiceId: "prac_123" alertTriggered: true, severity: "WARNING", alertType: "API_COST_HIGH", flagForReview: true src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.api-cost-normal dailyCost: 10, practiceId: "prac_123" alertTriggered: false src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.citation-success-alert successRate: 65, totalAttempted: 30 alertTriggered: true, severity: "ALERT", alertType: "CITATION_SUCCESS_LOW" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.citation-success-normal successRate: 85, totalAttempted: 30 alertTriggered: false src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.nap-mismatch-alert mismatches: 4, practiceId: "prac_123" alertTriggered: true, severity: "ALERT", alertType: "NAP_MISMATCH_HIGH" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.nap-mismatch-normal mismatches: 2, practiceId: "prac_123" alertTriggered: false src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.gbp-token-expiry expiresInDays: 5, practiceId: "prac_123" alertTriggered: true, severity: "ALERT", alertType: "GBP_TOKEN_EXPIRY" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.gbp-token-ok expiresInDays: 15, practiceId: "prac_123" alertTriggered: false src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.negative-review review: { rating: 2, status: "NEW", createdAt: <25h ago> } alertTriggered: true, severity: "ALERT", alertType: "NEGATIVE_REVIEW_UNREPLIED" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.payment-failed subscription: { status: "past_due" } alertTriggered: true, severity: "ALERT", alertType: "PAYMENT_FAILED" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.system-health-critical /api/health returns status: "degraded", db: false alertTriggered: true, severity: "CRITICAL", alertType: "SYSTEM_HEALTH_CRITICAL" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.disk-usage-critical diskUsage: 85% alertTriggered: true, severity: "CRITICAL", alertType: "DISK_USAGE_HIGH" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.memory-usage-critical memoryUsage: 92% alertTriggered: true, severity: "CRITICAL", alertType: "MEMORY_USAGE_HIGH" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.dlq-count-warning dlqCount: 12, window: "24h" alertTriggered: true, severity: "WARNING", alertType: "DLQ_HIGH" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.worker-cpu-warning cpu: 85%, duration: 6 (minutes) alertTriggered: true, severity: "WARNING", alertType: "WORKER_CPU_HIGH" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.duration-investigate baseline: 30s, current: 70s alertTriggered: true, severity: "WARNING", alertType: "DURATION_HIGH" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.priority-colors severity: "CRITICAL" Color #dc2626 (red) src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.priority-colors severity: "WARNING" Color #f59e0b (amber) src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.priority-colors severity: "LOW" Color #2563eb (blue) src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.response-sla severity: "CRITICAL" responseSLA: "15 minutes" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.response-sla severity: "WARNING" responseSLA: "2 hours" src/__tests__/unit/admin/alerts/thresholds.test.ts
alert.response-sla severity: "LOW" responseSLA: "24 hours" src/__tests__/unit/admin/alerts/thresholds.test.ts

12.2 Integration Tests — Alert Engine#

Test Setup Action Assertion File
alert.engine.queue-depth BullMQ queue with 150 waiting jobs Poll alert engine Alert sent to Slack #alerts with "QUEUE_DEPTH_HIGH"; auto-scaling triggered if configured src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.failure-rate 6 failed out of 100 jobs in 1 hour Poll alert engine Alert sent to Slack #alerts with "FAILURE_RATE_HIGH"; new job ingestion paused src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.api-cost Practice prac_123 daily AI cost = $55 Daily cost rollup Admin email sent with "API_COST_HIGH"; practice flagged for review; non-essential tasks throttled src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.citation-low Citation batch with 65% success rate Batch completion Admin + client email sent with "CITATION_SUCCESS_LOW"; manual review of failing directories triggered src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.nap-mismatch 4 NAP mismatches for prac_123 Monthly NAP check Admin Slack alert with "NAP_MISMATCH_HIGH"; citation update workflow scheduled src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.gbp-token GbpAccount with tokenExpiresAt: now + 5 days Daily token health check Admin email with "GBP_TOKEN_EXPIRY"; token refresh job triggered src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.negative-review Review with rating: 2, status: "NEW", createdAt: 25h ago Review monitor run Client + admin email with "NEGATIVE_REVIEW_UNREPLIED"; escalation to client src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.payment-failed Stripe webhook payment_failed Webhook received Client + admin email with "PAYMENT_FAILED"; retry schedule initiated; churn risk flagged src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.system-health /api/health returns status: "degraded" Health check poll PagerDuty + Slack alert with "SYSTEM_HEALTH_CRITICAL"; auto-restart triggered src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.disk-usage EC2 disk at 85% CloudWatch poll PagerDuty + Slack alert with "DISK_USAGE_HIGH"; auto-scale storage triggered src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.memory-usage Container memory at 92% Container metrics poll PagerDuty + Slack alert with "MEMORY_USAGE_HIGH"; container restart triggered src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.dlq-high 12 jobs in DLQ within 24h DLQ counter poll Slack #alerts with "DLQ_HIGH"; admin email with job list src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.worker-cpu Worker CPU at 85% for 6 minutes Metrics poll Slack #alerts with "WORKER_CPU_HIGH"; scale to next tier triggered src/__tests__/integration/admin/alerts/engine.test.ts
alert.engine.inngest-latency Inngest step takes 6 minutes Step completion Alert with "INNGEST_LATENCY_HIGH"; engineering team notified src/__tests__/integration/admin/alerts/engine.test.ts
alert.notification-format Any alert triggered Inspect notification Contains alert type, severity, affected client (if applicable), recommended action, timestamp src/__tests__/integration/admin/alerts/engine.test.ts
alert.dedup Same alert condition persists for 5 minutes Alert engine polls Only 1 alert sent; no duplicate spam within 15-minute dedup window src/__tests__/integration/admin/alerts/engine.test.ts
alert.resolve Queue depth drops from 150 to 50 Alert engine polls "Resolved: QUEUE_DEPTH_HIGH" notification sent; severity cleared src/__tests__/integration/admin/alerts/engine.test.ts

12.3 Integration Tests — Job Monitoring Metrics#

Test Setup Action Assertion File
metric.queue-depth 5 queues with known waiting counts Call admin.getQueueMetrics Returns accurate waiting count per queue; total matches sum src/__tests__/integration/admin/jobs/metrics.test.ts
metric.active-jobs 5 queues with known active counts Call admin.getQueueMetrics Returns accurate active count per queue src/__tests__/integration/admin/jobs/metrics.test.ts
metric.completed-jobs 5 queues with known completed counts Call admin.getQueueMetrics Returns accurate completed count per queue src/__tests__/integration/admin/jobs/metrics.test.ts
metric.failed-jobs 5 queues with known failed counts Call admin.getQueueMetrics Returns accurate failed count per queue src/__tests__/integration/admin/jobs/metrics.test.ts
metric.failure-rate-calculation 100 completed, 5 failed in 1 hour Calculate rolling rate failureRate = 4.76% (failed / (completed + failed)) src/__tests__/integration/admin/jobs/metrics.test.ts
metric.average-duration 10 jobs with durations [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] Calculate average avgDuration = 55.0s src/__tests__/integration/admin/jobs/metrics.test.ts
metric.duration-baseline gbp-post-publish baseline = 30s; current avg = 70s Compare to baseline durationAlert = true (70 > 2×30) src/__tests__/integration/admin/jobs/metrics.test.ts
metric.per-client-cost AiUsage for prac_123: 10 records today totaling $55 Daily rollup perClientDailyCost = $55.00; alert triggered src/__tests__/integration/admin/jobs/metrics.test.ts
metric.dashboard-refresh KPI dashboard loaded Wait 60s (mock) admin.getKPIs re-fetches; stat cards update with new data src/__tests__/integration/admin/jobs/metrics.test.ts
metric.job-monitor-refresh Job monitor loaded Wait 10s (mock) admin.listJobs re-fetches; table updates with new queue state src/__tests__/integration/admin/jobs/metrics.test.ts
metric.api-cost-refresh API cost chart loaded Wait 5 minutes (mock) admin.getKPIs re-fetches; chart updates with new cost data src/__tests__/integration/admin/jobs/metrics.test.ts
metric.citation-health-refresh Citation health loaded Wait 1 hour (mock) admin.getKPIs re-fetches; citation health card updates src/__tests__/integration/admin/jobs/metrics.test.ts
metric.revenue-refresh Revenue chart loaded Wait 1 hour (mock) admin.getKPIs re-fetches; revenue chart updates src/__tests__/integration/admin/jobs/metrics.test.ts

12.4 Success Criteria (Binary) — Job Monitoring & Alerts#

  • Queue depth alert triggers when any queue has > 100 waiting jobs (WARNING)
  • Job failure rate alert triggers when > 5% of jobs fail within 1-hour rolling window (WARNING)
  • API cost alert triggers when per-practice daily AI spend > $50 (WARNING); flags for review
  • Citation success rate alert triggers when < 70% per batch (ALERT); notifies admin + client
  • NAP mismatch alert triggers when > 3 mismatches per client (ALERT); schedules update workflow
  • GBP token expiry alert triggers when < 7 days until expiry (ALERT); triggers refresh job
  • Negative review alert triggers when 1-2 star review unreplied > 24h (ALERT); escalates to client
  • Payment failed alert triggers on Stripe/Razorpay payment_failed webhook (ALERT); initiates retry
  • System health alert triggers when /api/health returns non-200 (CRITICAL); PagerDuty + Slack
  • Disk usage alert triggers when > 80% (CRITICAL); auto-scales storage
  • Memory usage alert triggers when > 90% (CRITICAL); restarts container
  • DLQ count alert triggers when > 10 jobs in DLQ within 24h (WARNING); admin Slack + email
  • Worker CPU alert triggers when > 80% for > 5 minutes (WARNING); scales to next tier
  • Inngest latency alert triggers when any step takes > 5 minutes
  • Duration alert triggers when average job duration exceeds 2× baseline per job type
  • All alerts include: type, severity, affected entity, recommended action, timestamp
  • Alert deduplication prevents duplicate notifications within 15-minute window
  • Alert resolution notification sent when condition returns to normal
  • Critical alerts have 15-minute response SLA; Warning = 2 hours; Low = 24 hours
  • Job monitor dashboard refreshes every 10 seconds
  • KPI stat cards refresh every 60 seconds
  • API cost chart refreshes every 5 minutes
  • Citation health refreshes every 1 hour
  • Revenue chart refreshes every 1 hour
  • Queue metrics accurately reflect BullMQ Redis state
  • Failure rate calculation uses rolling 1-hour window
  • Average duration calculation correctly computes mean per job type
  • Per-client daily cost aggregation matches AiUsage table rollup

12.5 Agent Context (Pre-conditions) — Job Monitoring & Alerts#

  • Required DB state: BullMQ queues with known job counts (waiting/active/completed/failed), AiUsage (daily records per practice), Citation (batch data), GbpAccount (with varied tokenExpiresAt), Review (with negative unreplied), Subscription (with past due records), JobLog (with timestamps for rolling window), AlertLog (empty for dedup tests)
  • Required env vars: ALERT_SLACK_WEBHOOK_URL (mocked); ALERT_PAGERDUTY_KEY (mocked); ADMIN_EMAIL=admin@rankflow.in; QUEUE_DEPTH_THRESHOLD=100; FAILURE_RATE_THRESHOLD=0.05; API_COST_DAILY_THRESHOLD=50; CITATION_SUCCESS_THRESHOLD=0.70; NAP_MISMATCH_THRESHOLD=3; TOKEN_EXPIRY_DAYS_THRESHOLD=7; DLQ_THRESHOLD=10; CPU_THRESHOLD=80; CPU_DURATION_MINUTES=5; MEMORY_THRESHOLD=90; DISK_THRESHOLD=80; INNGEST_LATENCY_THRESHOLD_MINUTES=5; DURATION_BASELINE_MULTIPLIER=2
  • Required mocks: Slack webhook mocked; PagerDuty API mocked; email provider mocked; next/navigation mocked
  • Mock Redis state: BullMQ queues with known counts for metric accuracy tests
  • Alert engine state: Alert rules engine initialized with all threshold configs

12.6 Verification Commands — Job Monitoring & Alerts#

# Unit: alert threshold logic
pnpm test:unit -- src/__tests__/unit/admin/alerts/thresholds.test.ts

# Integration: alert engine
pnpm test:integration -- src/__tests__/integration/admin/alerts/engine.test.ts

# Integration: job metrics
pnpm test:integration -- src/__tests__/integration/admin/jobs/metrics.test.ts

# Full alert + monitoring suite
pnpm test:unit -- src/__tests__/unit/admin/alerts/
pnpm test:integration -- src/__tests__/integration/admin/alerts/
pnpm test:integration -- src/__tests__/integration/admin/jobs/metrics.test.ts

13. Cross-Cutting Integration Tests#

13.1 End-to-End Admin Flow Tests#

Test Setup Action Assertion File
e2e.admin-login-to-kpi Clean browser; admin credentials Login → navigate to /admin KPI dashboard loads with all 6 sections; data visible within 3 seconds e2e/admin/kpi.spec.ts
e2e.client-crud Admin logged in Navigate to /admin/clients → create client → edit → suspend → view detail → delete Each action succeeds; audit log reflects all 4 actions; no 500 errors e2e/admin/client-crud.spec.ts
e2e.job-retry-from-dlq Failed job in DLQ Navigate to /admin/jobs → filter FAILED → click Retry → verify job re-queued Job status changes from FAILED to PENDING; DLQ count decreases by 1 e2e/admin/job-retry.spec.ts
e2e.content-approval Content in PENDING_REVIEW Navigate to /admin/content → select piece → click Approve → verify status Content status changes to APPROVED; client dashboard shows published e2e/admin/content-approval.spec.ts
e2e.impersonation-flow Admin logged in; client with prac_123 Navigate to /admin/clients/prac_123 → click Impersonate → confirm → view client dashboard → exit Client dashboard renders correctly; impersonation banner visible; exit returns to admin e2e/admin/impersonation.spec.ts
e2e.billing-export Admin logged in Navigate to /admin/billing → click Export Revenue CSV → verify download CSV file downloaded with correct headers and monthly data e2e/admin/billing-export.spec.ts
e2e.report-generation Admin logged in Navigate to /admin/reports → select Citation Success Rate → export CSV CSV downloaded with weekly citation data e2e/admin/report-export.spec.ts
e2e.alert-notification Queue depth > 100 Trigger alert condition Slack webhook receives payload with correct alert type and severity e2e/admin/alert-notification.spec.ts
e2e.domain-revalidate Admin logged in; practice with directorySlug Navigate to /admin/directory → click Force Revalidate → verify success Toast shows "Domain revalidated"; ISR cache purged e2e/admin/directory-profile-revalidate.spec.ts
e2e.social-disconnect Admin logged in; active social account Navigate to /admin/social-connections → click Disconnect → confirm Account status changes to DISCONNECTED; Composio API called e2e/admin/social-disconnect.spec.ts

13.2 API Router Protection Tests#

Test Setup Action Assertion File
api.all-admin-procedures List of all 15+ admin tRPC procedures Call each with client role All return TRPCError with code FORBIDDEN src/__tests__/integration/admin/api.protection.test.ts
api.admin-procedures-list admin.getKPIs, admin.listClients, admin.getClient, admin.updateClient, admin.listDomains, admin.listWebsites, admin.listSocialConnections, admin.listJobs, admin.retryJob, admin.listContent, admin.approveContent, admin.updatePrompt, admin.getRevenue, admin.getFailedCharges, admin.impersonate, admin.endImpersonate Call each with admin role All return 200 with valid data shapes src/__tests__/integration/admin/api.protection.test.ts
api.mutation-protection All admin mutations Call each without session All return TRPCError with code UNAUTHORIZED src/__tests__/integration/admin/api.protection.test.ts

13.3 Success Criteria (Binary) — Cross-Cutting#

  • Admin login → /admin KPI dashboard loads within 3 seconds with all sections visible
  • Full client CRUD flow (create → edit → suspend → view → delete) completes without errors
  • Job retry from DLQ changes status from FAILED to PENDING; DLQ count decreases
  • Content approval from admin dashboard updates status to APPROVED and reflects on client dashboard
  • Impersonation flow: admin → client dashboard → exit → admin detail page works end-to-end
  • Billing CSV export downloads file with correct revenue data
  • Report CSV export downloads file with correct report data
  • Alert notification sends Slack payload with correct type and severity when threshold breached
  • Domain revalidate triggers ISR cache purge and shows success toast
  • Social disconnect updates status and calls Composio API
  • All 15+ admin tRPC procedures return 403 FORBIDDEN for non-admin roles
  • All admin tRPC procedures return 401 UNAUTHORIZED for unauthenticated requests
  • All admin tRPC procedures return 200 with valid data for authenticated admin
  • No admin API endpoint leaks data across practice boundaries (except list aggregated views)

13.4 Agent Context (Pre-conditions) — Cross-Cutting#

  • Required DB state: Full production-like dataset: 100 ClientProfile, 100 Location, 100 GbpAccount, 400 SocialAccount, 3000 Citation, 700 DirectoryProfileSection, 2000 JobLog, 1200 Invoice, 2000 AuditLog, 400 ContentPiece, 50 PromptTemplate, 80 Subscription, 500 AiUsage, 1000 EmailLog, 20 SupportTicket
  • Required env vars: All production env vars set to test values; NODE_ENV=test; E2E_BASE_URL=http://localhost:3000
  • Required external mocks: All third-party APIs mocked (Stripe, Razorpay, Google, Composio, Resend, Slack, PagerDuty, DataForSEO, SerpAPI, Hyperbrowser, Firecrawl, Claude, OpenAI)
  • Mock Redis state: Full BullMQ queue state with realistic job counts; DLQ populated with 10-20 failed jobs
  • Browser state: Playwright or Cypress with admin session cookie pre-set
  • Network mocks: msw server running for all external API calls

13.5 Verification Commands — Cross-Cutting#

# E2E: admin flows
pnpm test:e2e -- e2e/admin/kpi.spec.ts
pnpm test:e2e -- e2e/admin/client-crud.spec.ts
pnpm test:e2e -- e2e/admin/job-retry.spec.ts
pnpm test:e2e -- e2e/admin/content-approval.spec.ts
pnpm test:e2e -- e2e/admin/impersonation.spec.ts
pnpm test:e2e -- e2e/admin/billing-export.spec.ts
pnpm test:e2e -- e2e/admin/report-export.spec.ts
pnpm test:e2e -- e2e/admin/alert-notification.spec.ts
pnpm test:e2e -- e2e/admin/directory-profile-revalidate.spec.ts
pnpm test:e2e -- e2e/admin/social-disconnect.spec.ts

# Integration: API protection
pnpm test:integration -- src/__tests__/integration/admin/api.protection.test.ts

# Full E2E suite
pnpm test:e2e -- e2e/admin/

14. Execution Plan & Agent Assignments#

Phase 1: Component Infrastructure (Day 1)#

Agent Task Test Files Verification
Admin_UI_Specialist Set up admin component test harness (RTL, mock tRPC, mock session) src/__tests__/unit/admin/setup.ts pnpm test:unit -- admin passes with 0 tests
Admin_UI_Specialist Unit tests for all reusable admin components src/__tests__/unit/admin/Sidebar.test.tsx, Header.test.tsx, DataTable.test.tsx, StatCard.test.tsx, StatusBadge.test.tsx, ConfirmDialog.test.tsx, FilterBar.test.tsx, PracticeSelect.test.tsx, JsonViewer.test.tsx pnpm test:unit -- admin
Access_Control_Specialist Integration tests for layout, access control, responsive behavior src/__tests__/integration/admin/access.test.ts, responsive.test.tsx pnpm test:integration -- admin/access

Phase 2: Page Unit Tests (Days 2-3)#

Agent Task Test Files Verification
KPI_Dashboard_Specialist Unit tests for KPI page + all chart components src/__tests__/unit/admin/kpi/ pnpm test:unit -- admin/kpi
Client_Management_Specialist Unit tests for client list and client detail pages src/__tests__/unit/admin/clients/ pnpm test:unit -- admin/clients
Infrastructure_Specialist Unit tests for domain and website pages src/__tests__/unit/admin/directory/, websites/ pnpm test:unit -- admin/domains, admin/websites
Social_Content_Specialist Unit tests for social connections and content review pages src/__tests__/unit/admin/social/, content/ pnpm test:unit -- admin/social, admin/content
Job_Monitor_Specialist Unit tests for job monitor page + stat cards src/__tests__/unit/admin/jobs/ pnpm test:unit -- admin/jobs
Billing_Reports_Specialist Unit tests for billing and reports pages src/__tests__/unit/admin/billing/, reports/ pnpm test:unit -- admin/billing, admin/reports

Phase 3: API Integration Tests (Days 4-5)#

Agent Task Test Files Verification
Admin_API_Specialist Integration tests for all admin tRPC procedures src/__tests__/integration/admin/kpi/, clients/, infrastructure/, social/, jobs/, content/, billing/, reports/ pnpm test:integration -- admin/
KPI_Calculation_Specialist KPI calculation logic + DB aggregation accuracy src/__tests__/unit/admin/kpi/calculations.test.ts, src/__tests__/integration/admin/kpi/db-aggregation.test.ts pnpm test:unit -- admin/kpi/calculations, pnpm test:integration -- admin/kpi/db-aggregation
Impersonation_Specialist Impersonation feature unit + integration tests src/__tests__/unit/admin/impersonation/, src/__tests__/integration/admin/impersonation/ pnpm test:unit -- admin/impersonation, pnpm test:integration -- admin/impersonation
Alert_Engine_Specialist Alert threshold logic + alert engine integration src/__tests__/unit/admin/alerts/thresholds.test.ts, src/__tests__/integration/admin/alerts/engine.test.ts pnpm test:unit -- admin/alerts, pnpm test:integration -- admin/alerts
Job_Metrics_Specialist Job monitoring metrics + refresh intervals src/__tests__/integration/admin/jobs/metrics.test.ts, api.test.ts pnpm test:integration -- admin/jobs

Phase 4: E2E & Cross-Cutting (Day 6)#

Agent Task Test Files Verification
E2E_Admin_Specialist End-to-end admin flows e2e/admin/*.spec.ts pnpm test:e2e -- e2e/admin/
API_Protection_Specialist Router protection + role enforcement tests src/__tests__/integration/admin/api.protection.test.ts, api.access.test.ts pnpm test:integration -- admin/api.protection

Phase 5: Verification & Compliance (Day 7)#

Agent Task Verification
Test_Suite_Auditor Run full admin suite; verify all checkboxes pass pnpm test:unit -- admin/, pnpm test:integration -- admin/, pnpm test:e2e -- e2e/admin/
Coverage_Auditor Verify coverage thresholds for admin code: lines > 80%, functions > 85%, branches > 75% pnpm test:coverage -- src/app/(admin)/, src/components/admin/, src/server/api/routers/admin.ts
Smoke_Test_Agent Run admin dashboard smoke test against local dev server `curl -s http://localhost:3000/api/health/jobs

Appendix A: Test Data Constants#

Constant Value Usage
MOCK_ADMIN_ID usr_admin_001 Deterministic admin user ID
MOCK_CLIENT_ID usr_client_001 Deterministic client user ID
MOCK_EDITOR_ID usr_editor_001 Deterministic editor user ID
MOCK_PRACTICE_ID prac_test_001 Deterministic practice ID
MOCK_LOCATION_ID loc_test_001 Deterministic location ID
MOCK_DIRECTORY_SLUG dr-smith-dental.rankflow.in Deterministic directorySlug
MOCK_CUSTOM_DOMAIN drsmithdental.com Deterministic directory profile URL
MOCK_SOCIAL_ACCOUNT_ID soc_test_001 Deterministic social account ID
MOCK_JOB_ID job_test_001 Deterministic job ID
MOCK_CONTENT_ID content_test_001 Deterministic content piece ID
MOCK_INVOICE_ID inv_test_001 Deterministic invoice ID
CURRENCY_INR INR Default currency code
IMPERSONATION_TIMEOUT 120 Impersonation timeout in minutes
QUEUE_DEPTH_THRESHOLD 100 Queue depth warning threshold
FAILURE_RATE_THRESHOLD 0.05 Failure rate warning threshold (5%)
API_COST_DAILY_THRESHOLD 50 Per-practice daily AI cost threshold (USD)
CITATION_SUCCESS_THRESHOLD 0.70 Citation success rate alert threshold (70%)
NAP_MISMATCH_THRESHOLD 3 NAP mismatch alert threshold per client
TOKEN_EXPIRY_DAYS_THRESHOLD 7 GBP token expiry alert threshold (days)
DLQ_THRESHOLD 10 Dead letter queue warning threshold (24h)
CPU_THRESHOLD 80 Worker CPU warning threshold (%)
CPU_DURATION_MINUTES 5 CPU alert duration threshold
MEMORY_THRESHOLD 90 Memory usage critical threshold (%)
DISK_THRESHOLD 80 Disk usage critical threshold (%)
INNGEST_LATENCY_THRESHOLD 5 Inngest latency alert threshold (minutes)
DURATION_BASELINE_MULTIPLIER 2 Duration alert baseline multiplier
KPI_REFRESH_INTERVAL 60000 KPI dashboard refresh interval (ms)
JOB_MONITOR_REFRESH_INTERVAL 10000 Job monitor refresh interval (ms)
API_COST_REFRESH_INTERVAL 300000 API cost chart refresh interval (ms)
CITATION_REFRESH_INTERVAL 3600000 Citation health refresh interval (ms)
REVENUE_REFRESH_INTERVAL 3600000 Revenue chart refresh interval (ms)

Appendix B: Admin tRPC Procedure Registry#

Procedure Type Auth Required Role Required Returns
admin.getKPIs Query ADMIN AdminKPIs
admin.getSystemHealth Query ADMIN { status, checks }
admin.listClients Query ADMIN ClientProfile[] (paginated)
admin.getClient Query ADMIN ClientProfile + relations
admin.updateClient Mutation ADMIN ClientProfile
admin.listDomains Query ADMIN Domain[]
admin.checkDNS Query ADMIN DNSStatus
admin.revalidateDomain Mutation ADMIN { success }
admin.listWebsites Query ADMIN BlogSite[]
admin.checkWebsiteHealth Query ADMIN HealthStatus
admin.listSocialConnections Query ADMIN SocialAccount[]
admin.listJobs Query ADMIN JobLog[] (paginated)
admin.retryJob Mutation ADMIN { success, newJobId }
admin.getJobStats Query ADMIN JobStats
admin.getJobLogs Query ADMIN JobLogEntry[]
admin.listContent Query ADMIN ContentPiece[]
admin.approveContent Mutation ADMIN ContentPiece
admin.rejectContent Mutation ADMIN ContentPiece
admin.updatePrompt Mutation ADMIN PromptTemplate
admin.getRevenue Query ADMIN RevenueData
admin.getFailedCharges Query ADMIN FailedCharge[]
admin.getChurnReport Query ADMIN ChurnReport
admin.getCitationSuccessReport Query ADMIN CitationSuccessReport
admin.getApiCostReport Query ADMIN ApiCostReport
admin.getContentVolumeReport Query ADMIN ContentVolumeReport
admin.getSupportTicketReport Query ADMIN SupportTicketReport
admin.getGrossMarginReport Query ADMIN GrossMarginReport
admin.impersonate Mutation ADMIN Session
admin.endImpersonate Mutation ADMIN Session
admin.getQueueMetrics Query ADMIN QueueMetrics

End of Admin Dashboard & Observability Test Specification — RankFlow AI v1.0.0