From 3aca9d8f7a0e42542b3bb7d7f86d078b9c99bfd8 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 10 Jul 2026 14:22:57 +0100 Subject: [PATCH] test(frontend): branch-coverage for licensePresentation and patrolSummaryPresentation Add licensePresentation.coverage.test.ts (75 cases) and patrolSummaryPresentation.coverage.test.ts (98 cases) covering previously uncovered pure presentation/plan/verification functions flagged by the v8 branch-coverage backlog. New test files only; no source changes. --- .../licensePresentation.coverage.test.ts | 898 ++++++++++++ ...patrolSummaryPresentation.coverage.test.ts | 1257 +++++++++++++++++ 2 files changed, 2155 insertions(+) create mode 100644 frontend-modern/src/utils/__tests__/licensePresentation.coverage.test.ts create mode 100644 frontend-modern/src/utils/__tests__/patrolSummaryPresentation.coverage.test.ts diff --git a/frontend-modern/src/utils/__tests__/licensePresentation.coverage.test.ts b/frontend-modern/src/utils/__tests__/licensePresentation.coverage.test.ts new file mode 100644 index 0000000000..17c9880555 --- /dev/null +++ b/frontend-modern/src/utils/__tests__/licensePresentation.coverage.test.ts @@ -0,0 +1,898 @@ +/** + * Branch-coverage tests for licensePresentation.ts. + * + * Fills defensive / edge-case branches not already exercised by + * `licensePresentation.test.ts`. Each `it` targets a distinct branch + * (happy path, null / undefined input, fallback, or early-return). + */ +import { describe, expect, it } from 'vitest'; + +import { + getBillingAdminTrialStatus, + getCommercialMigrationActionText, + getCommercialMigrationNotice, + getFeatureMinTierLabel, + getGrandfatheredPriceContinuityNotice, + getLicenseFeatureLabel, + getLicenseSubscriptionStatusPresentation, + getLicenseTierLabel, + getSelfHostedActivationSuccessPresentation, + getSelfHostedCurrentPlanPresentation, + getSelfHostedCurrentPlanStatusPresentation, + getSelfHostedPlanComparisonPresentation, + getSelfHostedPlanLabel, + getSelfHostedPlanStatusPresentation, + isDisplayableLicenseFeature, + requiresPulseProRuntime, +} from '@/utils/licensePresentation'; +import { PATROL_CONTROL_PATH } from '@/routing/resourceLinks'; + +/* ------------------------------------------------------------------ * + * Label / feature lookup edge cases + * ------------------------------------------------------------------ */ + +describe('getLicenseTierLabel - edge cases', () => { + it('returns Unknown for empty, null, and undefined tier', () => { + expect(getLicenseTierLabel('')).toBe('Unknown'); + expect(getLicenseTierLabel(null)).toBe('Unknown'); + expect(getLicenseTierLabel(undefined)).toBe('Unknown'); + expect(getLicenseTierLabel(' ')).toBe('Unknown'); + }); + + it('returns mapped labels for every canonical tier key', () => { + expect(getLicenseTierLabel('relay')).toBe('Relay'); + expect(getLicenseTierLabel('pro')).toBe('Pro'); + expect(getLicenseTierLabel('pro_annual')).toBe('Pro Annual'); + expect(getLicenseTierLabel('lifetime')).toBe('Lifetime'); + expect(getLicenseTierLabel('cloud')).toBe('Cloud'); + expect(getLicenseTierLabel('msp')).toBe('MSP'); + }); + + it('title-cases unknown tiers as a fallback', () => { + expect(getLicenseTierLabel('team_seats')).toBe('Team Seats'); + }); +}); + +describe('getSelfHostedPlanLabel - edge cases', () => { + it('returns Unknown for empty, null, and undefined', () => { + expect(getSelfHostedPlanLabel('')).toBe('Unknown'); + expect(getSelfHostedPlanLabel(null)).toBe('Unknown'); + expect(getSelfHostedPlanLabel(undefined)).toBe('Unknown'); + }); + + it('maps pro_annual to Pulse Pro Annual', () => { + expect(getSelfHostedPlanLabel('pro_annual')).toBe('Pulse Pro Annual'); + }); + + it('falls back to getLicenseTierLabel for tiers not in SELF_HOSTED_PLAN_LABELS', () => { + expect(getSelfHostedPlanLabel('enterprise')).toBe('Enterprise'); + expect(getSelfHostedPlanLabel('custom_plan')).toBe('Custom Plan'); + }); +}); + +describe('getLicenseFeatureLabel - edge cases', () => { + it('returns Unknown for empty, null, and undefined', () => { + expect(getLicenseFeatureLabel('')).toBe('Unknown'); + expect(getLicenseFeatureLabel(null)).toBe('Unknown'); + expect(getLicenseFeatureLabel(undefined)).toBe('Unknown'); + }); +}); + +describe('isDisplayableLicenseFeature - edge cases', () => { + it('returns false for empty, null, and undefined', () => { + expect(isDisplayableLicenseFeature('')).toBe(false); + expect(isDisplayableLicenseFeature(null)).toBe(false); + expect(isDisplayableLicenseFeature(undefined)).toBe(false); + expect(isDisplayableLicenseFeature(' ')).toBe(false); + }); + + it('normalises case and whitespace before checking the catalog', () => { + expect(isDisplayableLicenseFeature('AI_PATROL')).toBe(true); + expect(isDisplayableLicenseFeature(' Relay ')).toBe(true); + }); +}); + +describe('getFeatureMinTierLabel - edge cases', () => { + it('returns Pro for empty, null, and undefined', () => { + expect(getFeatureMinTierLabel('')).toBe('Pro'); + expect(getFeatureMinTierLabel(null)).toBe('Pro'); + expect(getFeatureMinTierLabel(undefined)).toBe('Pro'); + }); + + it('returns Relay for push_notifications and mobile_app', () => { + expect(getFeatureMinTierLabel('push_notifications')).toBe('Relay'); + expect(getFeatureMinTierLabel('mobile_app')).toBe('Relay'); + }); +}); + +/* ------------------------------------------------------------------ * + * Subscription-status presentation + * ------------------------------------------------------------------ */ + +describe('getLicenseSubscriptionStatusPresentation - uncovered branches', () => { + it('returns Active and Suspended for their respective states', () => { + expect(getLicenseSubscriptionStatusPresentation('active')).toEqual({ + label: 'Active', + badgeClass: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300', + }); + expect(getLicenseSubscriptionStatusPresentation('suspended')).toEqual({ + label: 'Suspended', + badgeClass: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300', + }); + }); + + it('maps canceled to the same presentation as expired', () => { + expect(getLicenseSubscriptionStatusPresentation('canceled')).toEqual({ + label: 'Expired', + badgeClass: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300', + }); + }); + + it('normalises mixed-case and whitespace before matching', () => { + expect(getLicenseSubscriptionStatusPresentation(' TRIAL ').label).toBe('Trial'); + expect(getLicenseSubscriptionStatusPresentation('Grace').label).toBe('Grace Period'); + }); +}); + +describe('getSelfHostedCurrentPlanStatusPresentation - edge cases', () => { + it('maps community tier to the Community badge', () => { + expect( + getSelfHostedCurrentPlanStatusPresentation({ + tier: 'community', + subscription_state: 'active', + }), + ).toEqual({ + label: 'Community', + badgeClass: 'bg-surface text-base-content border border-border', + }); + }); + + it('delegates to getLicenseSubscriptionStatusPresentation for null entitlements', () => { + expect(getSelfHostedCurrentPlanStatusPresentation(null)).toEqual({ + label: 'Unknown', + badgeClass: 'bg-surface-alt text-muted', + }); + expect(getSelfHostedCurrentPlanStatusPresentation(undefined)).toEqual({ + label: 'Unknown', + badgeClass: 'bg-surface-alt text-muted', + }); + }); + + it('delegates to subscription-state presentation for a paid tier', () => { + expect( + getSelfHostedCurrentPlanStatusPresentation({ + tier: 'pro', + subscription_state: 'suspended', + }), + ).toEqual({ + label: 'Suspended', + badgeClass: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300', + }); + }); +}); + +/* ------------------------------------------------------------------ * + * requiresPulseProRuntime + * ------------------------------------------------------------------ */ + +describe('requiresPulseProRuntime - branch coverage', () => { + it('returns false for null and undefined entitlements', () => { + expect(requiresPulseProRuntime(null)).toBe(false); + expect(requiresPulseProRuntime(undefined)).toBe(false); + }); + + it('returns false when hosted_mode is true', () => { + expect( + requiresPulseProRuntime({ + tier: 'pro', + subscription_state: 'active', + hosted_mode: true, + }), + ).toBe(false); + }); + + it('returns false for tiers outside the Pro runtime set', () => { + expect( + requiresPulseProRuntime({ tier: 'relay', subscription_state: 'active' }), + ).toBe(false); + expect( + requiresPulseProRuntime({ tier: 'free', subscription_state: 'active' }), + ).toBe(false); + }); + + it('returns false for a Pro-tier with non-paid subscription state', () => { + expect( + requiresPulseProRuntime({ tier: 'pro', subscription_state: 'expired' }), + ).toBe(false); + }); + + it('returns true for active, grace, and trial Pro-tier entitlements', () => { + expect( + requiresPulseProRuntime({ tier: 'pro', subscription_state: 'active' }), + ).toBe(true); + expect( + requiresPulseProRuntime({ tier: 'pro', subscription_state: 'grace' }), + ).toBe(true); + expect( + requiresPulseProRuntime({ tier: 'pro', subscription_state: 'trial' }), + ).toBe(true); + }); + + it('returns true for every tier in the Pro-runtime-required set', () => { + for (const tier of ['pro', 'pro_annual', 'pro_plus', 'lifetime', 'enterprise']) { + expect( + requiresPulseProRuntime({ tier, subscription_state: 'active' }), + ).toBe(true); + } + }); +}); + +/* ------------------------------------------------------------------ * + * getGrandfatheredPriceContinuityNotice + * ------------------------------------------------------------------ */ + +describe('getGrandfatheredPriceContinuityNotice - edge cases', () => { + it('returns null for null / undefined plan version', () => { + expect(getGrandfatheredPriceContinuityNotice(null, 'active')).toBeNull(); + expect(getGrandfatheredPriceContinuityNotice(undefined, 'active')).toBeNull(); + }); + + it('returns null for a non-grandfathered plan version even when active', () => { + expect(getGrandfatheredPriceContinuityNotice('pro', 'active')).toBeNull(); + }); + + it('returns null for a grandfathered plan with a null subscription state', () => { + expect( + getGrandfatheredPriceContinuityNotice('v5_pro_monthly_grandfathered', null), + ).toBeNull(); + expect( + getGrandfatheredPriceContinuityNotice('v5_pro_annual_grandfathered', undefined), + ).toBeNull(); + }); + + it('returns null for a grandfathered plan with a non-active non-grace state', () => { + expect( + getGrandfatheredPriceContinuityNotice('v5_pro_monthly_grandfathered', 'suspended'), + ).toBeNull(); + }); +}); + +/* ------------------------------------------------------------------ * + * getCommercialMigrationActionText + * ------------------------------------------------------------------ */ + +describe('getCommercialMigrationActionText - all switch branches', () => { + it('returns retry guidance for retry_activation', () => { + expect(getCommercialMigrationActionText('retry_activation')).toBe( + 'Retry from this instance.', + ); + }); + + it('returns current-key guidance for use_v6_activation_key', () => { + expect(getCommercialMigrationActionText('use_v6_activation_key')).toBe( + 'Use the current v6 key for this purchase.', + ); + }); + + it('returns v5-key guidance for enter_supported_v5_key', () => { + expect(getCommercialMigrationActionText('enter_supported_v5_key')).toBe( + 'Retry with the original v5 Pro/Lifetime key from this instance.', + ); + }); + + it('returns support-contact guidance for free_installation_slot', () => { + expect(getCommercialMigrationActionText('free_installation_slot')).toContain( + 'Contact support@pulserelay.pro', + ); + }); + + it('returns retrieval guidance for retrieve_current_key', () => { + expect(getCommercialMigrationActionText('retrieve_current_key')).toContain( + 'pulserelay.pro/retrieve-license', + ); + }); + + it('returns egress guidance for allow_license_egress', () => { + expect(getCommercialMigrationActionText('allow_license_egress')).toContain( + 'Allow outbound HTTPS to license.pulserelay.pro', + ); + }); + + it('returns generic guidance for undefined and unknown actions', () => { + expect(getCommercialMigrationActionText(undefined)).toBe( + 'Review the plan state from this instance before trying again.', + ); + expect(getCommercialMigrationActionText('totally_unknown')).toBe( + 'Review the plan state from this instance before trying again.', + ); + }); +}); + +/* ------------------------------------------------------------------ * + * getCommercialMigrationNotice + * ------------------------------------------------------------------ */ + +describe('getCommercialMigrationNotice - uncovered branches', () => { + it('returns null for undefined migration', () => { + expect(getCommercialMigrationNotice(undefined)).toBeNull(); + }); + + it('returns null when migration has no state', () => { + expect(getCommercialMigrationNotice({})).toBeNull(); + expect(getCommercialMigrationNotice({ reason: 'exchange_invalid' })).toBeNull(); + }); + + it('renders pending conflict as settling handoff', () => { + const notice = getCommercialMigrationNotice({ + state: 'pending', + reason: 'exchange_conflict', + }); + expect(notice).toMatchObject({ + title: 'v5 license migration pending', + tone: expect.stringContaining('amber'), + }); + expect(notice?.body).toContain('still settling'); + }); + + it('uses the generic pending body for exchange_unavailable', () => { + const notice = getCommercialMigrationNotice({ + state: 'pending', + reason: 'exchange_unavailable', + }); + expect(notice?.body).toContain('did not complete yet'); + }); + + it('uses the generic pending body for an unrecognised reason', () => { + const notice = getCommercialMigrationNotice({ + state: 'pending', + reason: 'some_new_reason', + }); + expect(notice?.body).toContain('did not complete yet'); + }); + + it('renders malformed key as terminal', () => { + const notice = getCommercialMigrationNotice({ + state: 'failed', + reason: 'exchange_malformed', + }); + expect(notice?.body).toContain('malformed and cannot be migrated'); + }); + + it('renders revoked key as terminal', () => { + const notice = getCommercialMigrationNotice({ + state: 'failed', + reason: 'exchange_revoked', + }); + expect(notice?.body).toContain('no longer eligible for automatic migration'); + }); + + it('renders non-migratable key as terminal', () => { + const notice = getCommercialMigrationNotice({ + state: 'failed', + reason: 'exchange_non_migratable', + }); + expect(notice?.body).toContain('not eligible for automatic v6 migration'); + }); + + it('renders unsupported key as terminal', () => { + const notice = getCommercialMigrationNotice({ + state: 'failed', + reason: 'exchange_unsupported', + }); + expect(notice?.body).toContain('not a supported v5 Pro/Lifetime migration input'); + }); + + it('uses the generic failed body for an unrecognised reason', () => { + const notice = getCommercialMigrationNotice({ + state: 'failed', + reason: 'some_unknown_reason', + }); + expect(notice?.body).toContain('could not be migrated automatically'); + }); +}); + +/* ------------------------------------------------------------------ * + * getBillingAdminTrialStatus (exercises internal formatUnixSeconds) + * ------------------------------------------------------------------ */ + +describe('getBillingAdminTrialStatus - branch coverage', () => { + it('returns Loading for null and undefined state', () => { + expect(getBillingAdminTrialStatus(null)).toBe('Loading...'); + expect(getBillingAdminTrialStatus(undefined)).toBe('Loading...'); + }); + + it('returns No trial for a non-trial state with no trial timestamps', () => { + expect( + getBillingAdminTrialStatus({ subscription_state: 'active' } as never), + ).toBe('No trial'); + }); + + it('formats a trial state with only trial_ends_at', () => { + const result = getBillingAdminTrialStatus({ + subscription_state: 'trial', + trial_ends_at: 1_700_000_000, + } as never); + expect(result).toContain('Trial (ends'); + expect(result).not.toContain('started'); + }); + + it('formats a non-trial state that has trial timestamps', () => { + const result = getBillingAdminTrialStatus({ + subscription_state: 'active', + trial_started_at: 1_690_000_000, + trial_ends_at: 1_700_000_000, + } as never); + expect(result).toContain('Trial (started'); + expect(result).toContain('ends'); + }); + + it('returns N/A for a zero or negative timestamp via formatUnixSeconds', () => { + const result = getBillingAdminTrialStatus({ + subscription_state: 'active', + trial_started_at: 0, + trial_ends_at: 1_700_000_000, + } as never); + expect(result).toContain('started N/A'); + }); + + it('returns the raw string for a non-finite timestamp via formatUnixSeconds', () => { + const result = getBillingAdminTrialStatus({ + subscription_state: 'active', + trial_started_at: Infinity, + } as never); + expect(result).toContain('started Infinity'); + expect(result).toContain('ends N/A'); + }); +}); + +/* ------------------------------------------------------------------ * + * getSelfHostedPlanComparisonPresentation + * ------------------------------------------------------------------ */ + +describe('getSelfHostedPlanComparisonPresentation - edge cases', () => { + it('shows Relay and Pro cards for null / undefined entitlements', () => { + const result = getSelfHostedPlanComparisonPresentation({ entitlements: null }); + expect(result.cards).toHaveLength(2); + expect(result.cards[0].title).toBe('Relay plan'); + expect(result.cards[1].title).toBe('Pulse Pro plan'); + }); + + it('shows Relay and Pro cards for the community tier', () => { + const result = getSelfHostedPlanComparisonPresentation({ + entitlements: { + tier: 'community', + subscription_state: 'active', + capabilities: [], + limits: [], + upgrade_reasons: [], + }, + }); + expect(result.cards.map((c) => c.title)).toEqual(['Relay plan', 'Pulse Pro plan']); + }); + + it('returns no cards for an unrecognized tier', () => { + const result = getSelfHostedPlanComparisonPresentation({ + entitlements: { + tier: 'msp', + subscription_state: 'active', + capabilities: [], + limits: [], + upgrade_reasons: [], + }, + }); + expect(result.cards).toEqual([]); + }); +}); + +/* ------------------------------------------------------------------ * + * getSelfHostedCurrentPlanPresentation + * (exercises internal: isActiveOrGraceSubscription, + * getPatrolControlAction, getSelfHostedUnlockedFeatures, + * getSelfHostedIncludedExtras, hasGovernedDecisionOnlyPatrolOperatorOutcome) + * ------------------------------------------------------------------ */ + +describe('getSelfHostedCurrentPlanPresentation - null entitlements branch', () => { + it('returns the loading state when entitlements are null', () => { + expect( + getSelfHostedCurrentPlanPresentation({ + entitlements: null, + displayableCapabilities: [], + }), + ).toEqual({ + title: 'Current plan: Unknown', + body: 'Pulse is still loading the current self-hosted plan state for this instance.', + unlockedFeaturesLabel: 'Available on this instance', + unlockedFeatures: [], + includedExtras: [], + supplementalBadges: [], + }); + }); +}); + +describe('getSelfHostedCurrentPlanPresentation - trial branch', () => { + it('shows runtime-mismatch badge and private-runtime action for a Pro trial on community runtime', () => { + const result = getSelfHostedCurrentPlanPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'trial', + capabilities: ['ai_autofix'], + limits: [], + upgrade_reasons: [], + runtime: { build: 'community', label: 'Pulse Community runtime' }, + }, + displayableCapabilities: ['Patrol Handles Safe Fixes'], + }); + expect(result.title).toBe('Current plan: Pulse Pro Trial'); + expect(result.body).toContain('trial entitlement is active'); + expect(result.body).toContain('running the community runtime'); + expect(result.supplementalBadges).toContain('Pro runtime missing'); + expect(result.privateRuntimeAction).toEqual({ + actionLabel: 'Open Pulse Pro downloads', + actionUrl: 'https://pulserelay.pro/download.html', + }); + expect(result.patrolControlAction).toBeUndefined(); + }); + + it('shows active-trial body when unlocked features are present', () => { + const result = getSelfHostedCurrentPlanPresentation({ + entitlements: { + tier: 'relay', + subscription_state: 'trial', + capabilities: ['relay', 'mobile_app', 'push_notifications'], + limits: [], + upgrade_reasons: [], + }, + displayableCapabilities: [ + 'Pulse Relay (Remote Access)', + 'Pulse Mobile Pairing', + 'Push Notifications', + ], + }); + expect(result.title).toBe('Current plan: Relay Trial'); + expect(result.body).toBe('Relay trial capabilities are active on this instance right now.'); + }); + + it('shows being-confirmed body when no unlocked features are available', () => { + const result = getSelfHostedCurrentPlanPresentation({ + entitlements: { + tier: 'msp', + subscription_state: 'trial', + capabilities: [], + limits: [], + upgrade_reasons: [], + }, + displayableCapabilities: [], + }); + expect(result.title).toBe('Current plan: MSP Trial'); + expect(result.body).toBe('MSP trial entitlement is being confirmed for this instance.'); + expect(result.unlockedFeatures).toEqual([]); + }); +}); + +describe('getSelfHostedCurrentPlanPresentation - is_lifetime branch', () => { + it('pushes the grandfathered-lifetime badge for an active lifetime install', () => { + const result = getSelfHostedCurrentPlanPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'active', + is_lifetime: true, + capabilities: ['relay', 'mobile_app', 'ai_autofix'], + limits: [], + upgrade_reasons: [], + runtime: { build: 'pro', label: 'Pulse Pro runtime' }, + }, + displayableCapabilities: ['Pulse Relay (Remote Access)', 'Patrol Handles Safe Fixes'], + }); + expect(result.supplementalBadges).toContain('Grandfathered lifetime'); + expect(result.supplementalSummary).toContain( + 'migrated lifetime install remains valid permanently', + ); + }); +}); + +describe('getSelfHostedCurrentPlanPresentation - grace + grandfathered branch', () => { + it('shows the grandfathered-price badge for a grace-state recurring v5 plan', () => { + const result = getSelfHostedCurrentPlanPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'grace', + plan_version: 'v5_pro_annual_grandfathered', + capabilities: ['relay', 'ai_autofix'], + limits: [], + upgrade_reasons: [], + runtime: { build: 'pro', label: 'Pulse Pro runtime' }, + }, + displayableCapabilities: ['Pulse Relay (Remote Access)', 'Patrol Handles Safe Fixes'], + }); + expect(result.supplementalBadges).toContain('Grandfathered price'); + }); +}); + +describe('getSelfHostedCurrentPlanPresentation - fallback branch', () => { + it('returns the review-details fallback for a suspended pro plan', () => { + const result = getSelfHostedCurrentPlanPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'suspended', + capabilities: ['relay', 'ai_autofix'], + limits: [], + upgrade_reasons: [], + }, + displayableCapabilities: ['Pulse Relay (Remote Access)'], + }); + expect(result.title).toBe('Current plan: Pulse Pro'); + expect(result.body).toBe( + 'Review the plan details below to confirm what this key enables on this instance.', + ); + expect(result.unlockedFeaturesLabel).toBe('Available on this instance'); + expect(result.patrolControlAction).toBeUndefined(); + }); +}); + +describe('getSelfHostedCurrentPlanPresentation - getPatrolControlAction branches', () => { + it('omits patrolControlAction for an active Pro plan without the ai_autofix capability', () => { + const result = getSelfHostedCurrentPlanPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'active', + capabilities: ['relay'], + limits: [], + upgrade_reasons: [], + runtime: { build: 'pro', label: 'Pulse Pro runtime' }, + }, + displayableCapabilities: ['Pulse Relay (Remote Access)'], + }); + expect(result.patrolControlAction).toBeUndefined(); + }); + + it('uses patrol-control decision stage when governed decision has no value state', () => { + const result = getSelfHostedCurrentPlanPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'active', + capabilities: ['relay', 'mobile_app', 'ai_autofix'], + limits: [], + upgrade_reasons: [], + runtime: { build: 'pro', label: 'Pulse Pro runtime' }, + }, + displayableCapabilities: ['Pulse Relay (Remote Access)', 'Patrol Handles Safe Fixes'], + patrolOperatorStatus: { + patrolControlOperationsLoopStarterCount: 1, + patrolControlCompletedOperationsLoopCount: 1, + patrolControlResolvedOperationsLoopCount: 0, + externalAgentReady: false, + }, + }); + expect(result.patrolControlAction).toMatchObject({ + actionLabel: 'Review Patrol decision', + actionUrl: PATROL_CONTROL_PATH, + actionIntent: 'patrol_control', + }); + }); +}); + +/* ------------------------------------------------------------------ * + * getSelfHostedPlanStatusPresentation + * ------------------------------------------------------------------ */ + +describe('getSelfHostedPlanStatusPresentation - null-return branches', () => { + it('returns null for null and undefined entitlements', () => { + expect(getSelfHostedPlanStatusPresentation(null)).toBeNull(); + expect(getSelfHostedPlanStatusPresentation(undefined)).toBeNull(); + }); + + it('returns null for a tier with no plan definition', () => { + expect( + getSelfHostedPlanStatusPresentation({ + tier: 'enterprise', + subscription_state: 'active', + capabilities: [], + limits: [], + upgrade_reasons: [], + max_history_days: 90, + }), + ).toBeNull(); + }); + + it('returns null when max_history_days is missing', () => { + expect( + getSelfHostedPlanStatusPresentation({ + tier: 'relay', + subscription_state: 'active', + capabilities: ['relay'], + limits: [], + upgrade_reasons: [], + }), + ).toBeNull(); + }); + + it('returns null for an expired subscription state', () => { + expect( + getSelfHostedPlanStatusPresentation({ + tier: 'relay', + subscription_state: 'expired', + capabilities: ['relay'], + limits: [], + upgrade_reasons: [], + max_history_days: 14, + }), + ).toBeNull(); + }); +}); + +describe('getSelfHostedPlanStatusPresentation - trial and missing-history states', () => { + it('builds status for a trial Relay plan', () => { + const result = getSelfHostedPlanStatusPresentation({ + tier: 'relay', + subscription_state: 'trial', + capabilities: ['relay', 'mobile_app', 'push_notifications'], + limits: [], + upgrade_reasons: [], + max_history_days: 14, + }); + expect(result?.title).toBe('Relay status'); + expect(result?.items).toHaveLength(2); + expect(result?.items.every((i) => i.state === 'active')).toBe(true); + }); + + it('reports missing metric history when max_history_days is 0', () => { + const result = getSelfHostedPlanStatusPresentation({ + tier: 'relay', + subscription_state: 'active', + capabilities: ['relay', 'mobile_app', 'push_notifications'], + limits: [], + upgrade_reasons: [], + max_history_days: 0, + }); + const historyItem = result?.items.find((i) => i.label.includes('metric history')); + expect(historyItem).toMatchObject({ + state: 'missing', + statusLabel: 'Needs attention', + }); + expect(historyItem?.detail).toContain('does not have metric-history access yet'); + }); + + it('reports partial metric history when days are below the required amount', () => { + const result = getSelfHostedPlanStatusPresentation({ + tier: 'relay', + subscription_state: 'active', + capabilities: ['relay', 'mobile_app', 'push_notifications'], + limits: [], + upgrade_reasons: [], + max_history_days: 7, + }); + const historyItem = result?.items.find((i) => i.label.includes('metric history')); + expect(historyItem?.state).toBe('partial'); + expect(historyItem?.detail).toContain('below the expected'); + }); +}); + +/* ------------------------------------------------------------------ * + * getSelfHostedActivationSuccessPresentation + * (exercises internal: getSelfHostedActivationHighlights) + * ------------------------------------------------------------------ */ + +describe('getSelfHostedActivationSuccessPresentation - null-return branches', () => { + it('returns null when source is null', () => { + expect( + getSelfHostedActivationSuccessPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'active', + capabilities: [], + limits: [], + upgrade_reasons: [], + }, + displayableCapabilities: [], + source: null, + }), + ).toBeNull(); + }); + + it('returns null when entitlements are null', () => { + expect( + getSelfHostedActivationSuccessPresentation({ + entitlements: null, + displayableCapabilities: [], + source: 'manual', + }), + ).toBeNull(); + }); +}); + +describe('getSelfHostedActivationSuccessPresentation - purchase + runtime mismatch', () => { + it('renders amber tone and download action for a purchase with community runtime', () => { + const result = getSelfHostedActivationSuccessPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'active', + capabilities: ['relay', 'ai_autofix'], + limits: [], + upgrade_reasons: [], + runtime: { build: 'community', label: 'Pulse Community runtime' }, + }, + displayableCapabilities: ['Pulse Relay (Remote Access)', 'Patrol Handles Safe Fixes'], + source: 'purchase', + }); + expect(result).not.toBeNull(); + expect(result?.tone).toContain('amber'); + expect(result?.title).toBe('Pulse Pro license is active'); + expect(result?.body).toContain('Checkout completed and the license is active'); + expect(result?.body).toContain('running the community runtime'); + expect(result?.highlightsLabel).toBe('Licensed capabilities'); + expect(result?.actionLabel).toBe('Open Pulse Pro downloads'); + expect(result?.actionUrl).toBe('https://pulserelay.pro/download.html'); + }); +}); + +describe('getSelfHostedActivationSuccessPresentation - purchase without patrol action', () => { + it('renders the now-running body for a Relay purchase with no patrol capability', () => { + const result = getSelfHostedActivationSuccessPresentation({ + entitlements: { + tier: 'relay', + subscription_state: 'active', + capabilities: ['relay', 'mobile_app', 'push_notifications'], + limits: [], + upgrade_reasons: [], + }, + displayableCapabilities: [ + 'Pulse Relay (Remote Access)', + 'Pulse Mobile Pairing', + 'Push Notifications', + ], + source: 'purchase', + }); + expect(result).not.toBeNull(); + expect(result?.title).toBe('Relay is now active'); + expect(result?.body).toBe('Checkout completed and this instance is now running Relay.'); + expect(result?.tone).toContain('green'); + expect(result?.highlightsLabel).toBe('Available now on this instance'); + expect(result?.actionLabel).toBeUndefined(); + expect(result?.actionUrl).toBeUndefined(); + }); +}); + +describe('getSelfHostedActivationSuccessPresentation - manual + runtime mismatch', () => { + it('renders the key-accepted body for a manual activation with missing runtime identity', () => { + const result = getSelfHostedActivationSuccessPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'active', + capabilities: ['relay', 'audit_logging', 'rbac', 'ai_autofix'], + limits: [], + upgrade_reasons: [], + }, + displayableCapabilities: ['Pulse Relay (Remote Access)', 'Audit Logging'], + source: 'manual', + }); + expect(result).not.toBeNull(); + expect(result?.body).toContain('license key was accepted'); + expect(result?.body).toContain('not reporting the private Pulse Pro runtime'); + }); +}); + +describe('getSelfHostedActivationSuccessPresentation - highlights dedup', () => { + it('deduplicates overlapping plan highlights and displayable capabilities', () => { + const result = getSelfHostedActivationSuccessPresentation({ + entitlements: { + tier: 'pro', + subscription_state: 'active', + capabilities: ['relay', 'mobile_app', 'push_notifications', 'ai_autofix'], + limits: [], + upgrade_reasons: [], + runtime: { build: 'pro', label: 'Pulse Pro runtime' }, + }, + displayableCapabilities: [ + 'Patrol Investigates Issues', + 'Patrol Handles Safe Fixes', + '90-day metric history', + ], + source: 'manual', + }); + expect(result).not.toBeNull(); + const highlights = result?.highlights ?? []; + expect(highlights.length).toBe(new Set(highlights).size); + expect(highlights).toContain('Patrol Investigates Issues'); + expect(highlights).toContain('Role-Based Access Control (RBAC)'); + }); +}); diff --git a/frontend-modern/src/utils/__tests__/patrolSummaryPresentation.coverage.test.ts b/frontend-modern/src/utils/__tests__/patrolSummaryPresentation.coverage.test.ts new file mode 100644 index 0000000000..54786c88bd --- /dev/null +++ b/frontend-modern/src/utils/__tests__/patrolSummaryPresentation.coverage.test.ts @@ -0,0 +1,1257 @@ +import { describe, expect, it } from 'vitest'; + +import type { PatrolRunRecord } from '@/api/patrol'; +import type { IntelligenceHealthScore } from '@/types/aiIntelligence'; + +import { + getPatrolAssessmentPresentation, + getPatrolAssessmentShellPresentation, + getPatrolCompactAssessmentLabel, + getPatrolRecencyPresentation, + getPatrolSummaryMetricState, + getPatrolVerificationPresentation, +} from '@/utils/patrolSummaryPresentation'; + +// --------------------------------------------------------------------------- +// Fixture builders — minimal, typed objects that satisfy the module's +// internal PatrolAssessmentFinding / PatrolRunRecord / IntelligenceHealthScore +// shapes without needing the non-exported PatrolAssessmentFinding alias. +// --------------------------------------------------------------------------- + +type AssessmentFinding = NonNullable< + Parameters[0]['activeFindings'] +>[number]; + +function makeFinding(overrides: Partial = {}): AssessmentFinding { + return { + resourceId: 'vm-100', + resourceName: 'web-1', + title: 'Disk nearly full', + severity: 'warning', + status: 'active', + ...overrides, + }; +} + +function makeRuntimeFinding( + overrides: Partial = {}, +): AssessmentFinding { + return makeFinding({ + resourceId: 'ai-service', + resourceName: 'Pulse Patrol Service', + title: 'Pulse Patrol: Provider connection issue', + ...overrides, + }); +} + +function makeRun(overrides: Partial = {}): PatrolRunRecord { + return { + id: 'run-1', + started_at: '2026-07-10T09:00:00Z', + completed_at: '2026-07-10T09:05:00Z', + duration_ms: 300000, + type: 'patrol', + resources_checked: 0, + nodes_checked: 0, + guests_checked: 0, + docker_checked: 0, + storage_checked: 0, + hosts_checked: 0, + truenas_checked: 0, + pbs_checked: 0, + pmg_checked: 0, + kubernetes_checked: 0, + new_findings: 0, + existing_findings: 0, + rejected_findings: 0, + resolved_findings: 0, + auto_fix_count: 0, + findings_summary: '', + error_count: 0, + status: 'healthy', + triage_flags: 0, + tool_call_count: 0, + ...overrides, + }; +} + +function makeHealth( + overrides: Partial = {}, +): IntelligenceHealthScore { + return { + score: 90, + grade: 'A', + trend: 'stable', + factors: [], + prediction: 'Infrastructure is healthy with no significant issues detected.', + ...overrides, + }; +} + +function coverageFactor() { + return { + name: 'Patrol coverage incomplete', + impact: -0.35, + description: 'Patrol coverage is incomplete.', + category: 'coverage', + }; +} + +function successfulFullRun(resourcesChecked = 50): PatrolRunRecord { + return makeRun({ + type: 'patrol', + resources_checked: resourcesChecked, + error_count: 0, + status: 'issues_found', + }); +} + +// =========================================================================== +// getPatrolAssessmentShellPresentation — covers all SemanticTone map entries +// and the fallback for an unknown tone. +// =========================================================================== + +describe('getPatrolAssessmentShellPresentation', () => { + it('maps the success tone to the emerald shell', () => { + expect(getPatrolAssessmentShellPresentation('success')).toEqual({ + headerClass: 'bg-emerald-50/60 dark:bg-emerald-950/30', + badgeVariant: 'success', + iconClass: 'text-emerald-600 dark:text-emerald-300', + iconContainerClass: + 'border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-950/40', + }); + }); + + it('maps the error tone to the red shell with a danger badge', () => { + expect(getPatrolAssessmentShellPresentation('error')).toEqual({ + headerClass: 'bg-red-50/70 dark:bg-red-950/30', + badgeVariant: 'danger', + iconClass: 'text-red-600 dark:text-red-300', + iconContainerClass: + 'border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950/40', + }); + }); + + it('maps an explicit info tone to the blue shell', () => { + expect(getPatrolAssessmentShellPresentation('info')).toEqual({ + headerClass: 'bg-blue-50/70 dark:bg-blue-950/30', + badgeVariant: 'info', + iconClass: 'text-blue-600 dark:text-blue-300', + iconContainerClass: + 'border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950/40', + }); + }); + + it('falls back to the info shell for an unrecognised tone string', () => { + expect(getPatrolAssessmentShellPresentation('critical' as never)).toEqual( + getPatrolAssessmentShellPresentation('info'), + ); + }); +}); + +// =========================================================================== +// getPatrolSummaryMetricState — covers classifyActiveFindings severity +// branches and default fallbacks. +// =========================================================================== + +describe('getPatrolSummaryMetricState', () => { + it('flags primarySeverity as critical when an infrastructure critical finding is active', () => { + const state = getPatrolSummaryMetricState({ + activeFindings: [ + makeFinding({ severity: 'critical', resourceId: 'vm-1', title: 'Host down' }), + ], + }); + expect(state.primarySeverity).toBe('critical'); + expect(state.primaryValue).toBe(1); + expect(state.criticalValue).toBe(1); + expect(state.secondarySeverity).toBe('warning'); + }); + + it('flags secondarySeverity as critical when the runtime issue is critical', () => { + const state = getPatrolSummaryMetricState({ + activeFindings: [makeRuntimeFinding({ severity: 'critical' })], + }); + expect(state.secondarySeverity).toBe('critical'); + expect(state.secondaryValue).toBe(1); + expect(state.criticalValue).toBe(1); + expect(state.primaryLabel).toBe('Infrastructure findings'); + }); + + it('defaults fixedValue to 0 when fixedCount is omitted', () => { + expect(getPatrolSummaryMetricState({}).fixedValue).toBe(0); + }); + + it('ignores findings whose status is not active', () => { + const state = getPatrolSummaryMetricState({ + activeFindings: [ + makeFinding({ status: 'resolved', severity: 'critical' }), + makeRuntimeFinding({ status: 'dismissed', severity: 'critical' }), + ], + }); + expect(state.primaryValue).toBe(0); + expect(state.secondaryValue).toBe(0); + expect(state.criticalValue).toBe(0); + }); + + it('combines mixed infrastructure and runtime severities into the critical total', () => { + const state = getPatrolSummaryMetricState({ + activeFindings: [ + makeFinding({ severity: 'critical', resourceId: 'vm-1' }), + makeRuntimeFinding({ severity: 'critical' }), + makeFinding({ severity: 'warning', resourceId: 'vm-2' }), + ], + }); + expect(state.criticalValue).toBe(2); + expect(state.primarySeverity).toBe('critical'); + expect(state.secondarySeverity).toBe('critical'); + }); +}); + +// =========================================================================== +// getPatrolCompactAssessmentLabel — covers formatIssueLabel singular/plural, +// totalActive fallback, health-score suppression by absence. +// =========================================================================== + +describe('getPatrolCompactAssessmentLabel', () => { + it('lists both critical and warning infrastructure findings when both are active', () => { + expect( + getPatrolCompactAssessmentLabel({ + assessmentLabel: 'Issues detected', + activeFindings: [ + makeFinding({ severity: 'critical', resourceId: 'vm-1', title: 'Host down' }), + makeFinding({ severity: 'warning', resourceId: 'vm-2', title: 'Disk full' }), + ], + }), + ).toBe('1 critical issue · 1 warning issue'); + }); + + it('pluralises infrastructure critical issues', () => { + expect( + getPatrolCompactAssessmentLabel({ + assessmentLabel: 'Issues detected', + activeFindings: [ + makeFinding({ severity: 'critical', resourceId: 'vm-1' }), + makeFinding({ severity: 'critical', resourceId: 'vm-2' }), + ], + }), + ).toBe('2 critical issues'); + }); + + it('pluralises Patrol runtime issues when more than one is active', () => { + expect( + getPatrolCompactAssessmentLabel({ + assessmentLabel: 'Issues detected', + activeFindings: [ + makeRuntimeFinding({ severity: 'warning', title: 'Pulse Patrol: Issue A' }), + makeRuntimeFinding({ severity: 'warning', title: 'Pulse Patrol: Issue B' }), + ], + }), + ).toBe('2 Patrol runtime issues'); + }); + + it('falls back to the totalActive count when classification yields no parts', () => { + expect( + getPatrolCompactAssessmentLabel({ + assessmentLabel: 'Issues detected', + totalActive: 3, + activeFindings: [makeFinding({ status: 'resolved', severity: 'warning' })], + }), + ).toBe('3 active issues'); + }); + + it('omits the health-score segment when overallHealth is absent', () => { + expect( + getPatrolCompactAssessmentLabel({ + assessmentLabel: 'No active issues', + }), + ).toBe('No active issues'); + }); + + it('rounds the health score to the nearest whole number', () => { + expect( + getPatrolCompactAssessmentLabel({ + assessmentLabel: 'No active issues', + overallHealth: makeHealth({ score: 72.4 }), + }), + ).toBe('No active issues · health score 72/100'); + }); + + it('pluralises historical regressions', () => { + expect( + getPatrolCompactAssessmentLabel({ + assessmentLabel: 'No active issues', + historicalRegressionCount: 3, + }), + ).toBe('No active issues · 3 past regressions'); + }); + + it('combines findings, regressions, and health score into one compact label', () => { + expect( + getPatrolCompactAssessmentLabel({ + assessmentLabel: 'Issues detected', + overallHealth: makeHealth({ score: 60, grade: 'C' }), + activeFindings: [makeFinding({ severity: 'critical', resourceId: 'vm-1' })], + historicalRegressionCount: 1, + }), + ).toBe('1 critical issue · 1 past regression · health score 60/100'); + }); +}); + +// =========================================================================== +// getPatrolAssessmentPresentation — runtime-state branches, critical-finding +// branches, coverage-gap health tones (getHealthSummaryTone), and +// getCoverageDescription fallback. +// =========================================================================== + +describe('getPatrolAssessmentPresentation — runtime state', () => { + it('maps the blocked state to the paused runtime presentation', () => { + expect(getPatrolAssessmentPresentation({ runtimeState: 'blocked' })).toEqual({ + title: 'Patrol paused', + description: + 'Patrol cannot check infrastructure until the blocking condition is cleared.', + eyebrow: 'Patrol paused', + compactLabel: 'Patrol paused', + tone: 'warning', + }); + }); + + it('maps the disabled state to the disabled runtime presentation', () => { + expect(getPatrolAssessmentPresentation({ runtimeState: 'disabled' })).toEqual({ + title: 'Patrol disabled', + description: 'Enable Patrol to resume checks.', + eyebrow: 'Patrol disabled', + compactLabel: 'Patrol disabled', + tone: 'info', + }); + }); + + it('maps the unavailable state to the unavailable runtime presentation', () => { + expect(getPatrolAssessmentPresentation({ runtimeState: 'unavailable' })).toEqual({ + title: 'Patrol unavailable', + description: + 'Patrol is not ready yet. Check Provider & Models and runtime availability.', + eyebrow: 'Patrol unavailable', + compactLabel: 'Patrol unavailable', + tone: 'error', + }); + }); + + it('passes the blocked reason through to the assessment description', () => { + expect( + getPatrolAssessmentPresentation({ + runtimeState: 'blocked', + blockedReason: ' provider offline ', + }).description, + ).toBe('provider offline'); + }); +}); + +describe('getPatrolAssessmentPresentation — critical findings', () => { + it('reports critical issues detected for infrastructure critical findings', () => { + expect( + getPatrolAssessmentPresentation({ + criticalFindings: 1, + activeFindings: [ + makeFinding({ severity: 'critical', resourceId: 'vm-1', title: 'Host down' }), + ], + }), + ).toEqual({ + title: 'Critical issues detected', + description: + 'Patrol surfaced 1 active critical finding in your infrastructure. Review the active findings for more detail.', + eyebrow: 'Status', + compactLabel: 'Issues detected', + tone: 'error', + }); + }); + + it('reports a critical Patrol runtime issue when only a runtime finding is critical', () => { + expect( + getPatrolAssessmentPresentation({ + criticalFindings: 1, + activeFindings: [makeRuntimeFinding({ severity: 'critical' })], + }), + ).toEqual({ + title: 'Critical Patrol runtime issue', + description: + 'Patrol is currently blocked by a critical runtime issue: Provider connection issue. Review the Patrol runtime issue for more detail.', + eyebrow: 'Status', + compactLabel: 'Patrol runtime issue', + tone: 'error', + }); + }); +}); + +describe('getPatrolAssessmentPresentation — coverage gap health tones (getHealthSummaryTone)', () => { + it('uses the error tone when health grade is D', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 45, + grade: 'D', + factors: [coverageFactor()], + prediction: 'Coverage is incomplete.', + }), + }), + ).toMatchObject({ title: 'Coverage incomplete', tone: 'error' }); + }); + + it('uses the error tone when health grade is F', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 25, + grade: 'F', + factors: [coverageFactor()], + prediction: 'Coverage is incomplete.', + }), + }), + ).toMatchObject({ title: 'Coverage incomplete', tone: 'error' }); + }); + + it('uses the warning tone when health grade is C with a coverage gap', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 70, + grade: 'C', + factors: [coverageFactor()], + prediction: 'Coverage is incomplete.', + }), + }), + ).toMatchObject({ title: 'Coverage incomplete', tone: 'warning' }); + }); +}); + +describe('getPatrolAssessmentPresentation — getCoverageDescription fallback', () => { + it('falls back to the default message when the prediction is whitespace-only', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 50, + grade: 'D', + factors: [coverageFactor()], + prediction: ' ', + }), + }).description, + ).toBe('Patrol has not finished a current check.'); + }); +}); + +describe('getPatrolAssessmentPresentation — health requires attention', () => { + it('uses a usable prediction as the description for non-A health', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 80, + grade: 'B', + prediction: 'Some warnings need review.', + }), + }), + ).toEqual({ + title: 'Health requires attention', + description: 'Some warnings need review.', + eyebrow: 'Status', + compactLabel: 'Health requires attention', + tone: 'warning', + }); + }); + + it('suppresses a coverage-gap prediction after a verified full run', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 80, + grade: 'B', + factors: [coverageFactor()], + prediction: 'Coverage is incomplete. Run Patrol to check everything.', + }), + runs: [successfulFullRun()], + }), + ).toEqual({ + title: 'Health requires attention', + description: 'Patrol still needs attention.', + eyebrow: 'Status', + compactLabel: 'Health requires attention', + tone: 'warning', + }); + }); + + it('falls back when the health prediction is empty', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ score: 80, grade: 'B', prediction: '' }), + }).description, + ).toBe('Patrol still needs attention.'); + }); + + it('keeps a non-coverage-gap prediction even after a verified full run', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 80, + grade: 'B', + factors: [coverageFactor()], + prediction: 'A few warnings were detected.', + }), + runs: [successfulFullRun()], + }).description, + ).toBe('A few warnings were detected.'); + }); +}); + +describe('getPatrolAssessmentPresentation — default success', () => { + it('returns the default success shell when no health is provided', () => { + expect(getPatrolAssessmentPresentation({})).toEqual({ + title: 'No active issues detected', + description: 'Infrastructure is healthy with no significant issues detected.', + eyebrow: 'Status', + compactLabel: 'No active issues', + tone: 'success', + }); + }); + + it('falls back to the default description when grade-A health has an empty prediction', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ grade: 'A', prediction: '' }), + }).description, + ).toBe('Infrastructure is healthy with no significant issues detected.'); + }); +}); + +// =========================================================================== +// getFindingAssessmentDescription (private) — exercised through +// getPatrolAssessmentPresentation. Covers single-runtime-issue branches, +// joinAssessmentParts with 1/2/3+ parts, formatRuntimeIssueCount, +// getRuntimeFindingSummaryLabel, shouldUsePrediction, +// predictionReadsAsAllClear, predictionReadsAsCoverageGap, +// criticalFindings/warningFindings fallbacks. +// =========================================================================== + +describe('getFindingAssessmentDescription — single runtime issue', () => { + it('uses the critical-severity runtime summary for a single critical runtime issue', () => { + expect( + getPatrolAssessmentPresentation({ + criticalFindings: 1, + activeFindings: [makeRuntimeFinding({ severity: 'critical' })], + }).description, + ).toContain('Patrol is currently blocked by a critical runtime issue'); + }); + + it('appends the coverage-gap caveat for a single critical runtime issue', () => { + expect( + getPatrolAssessmentPresentation({ + criticalFindings: 1, + overallHealth: makeHealth({ + score: 50, + grade: 'D', + factors: [coverageFactor()], + prediction: 'Coverage is incomplete.', + }), + activeFindings: [makeRuntimeFinding({ severity: 'critical' })], + }).description, + ).toBe( + 'Patrol is currently blocked by a critical runtime issue: Provider connection issue. Recent coverage is incomplete. Run Patrol to check everything.', + ); + }); + + it('uses a usable prediction for a single runtime issue', () => { + expect( + getPatrolAssessmentPresentation({ + warningFindings: 1, + overallHealth: makeHealth({ + score: 85, + grade: 'B', + prediction: 'A runtime hiccup was observed.', + }), + activeFindings: [makeRuntimeFinding({ severity: 'warning' })], + }).description, + ).toBe('A runtime hiccup was observed.'); + }); + + it('falls back to "a Patrol runtime issue" when the runtime finding has no title', () => { + expect( + getPatrolAssessmentPresentation({ + warningFindings: 1, + activeFindings: [makeRuntimeFinding({ title: '' })], + }).description, + ).toBe( + 'Patrol has an active runtime issue: a Patrol runtime issue. Review the Patrol runtime issue for more detail.', + ); + }); + + it('keeps an unnormalised runtime title that has no Pulse Patrol prefix', () => { + expect( + getPatrolAssessmentPresentation({ + warningFindings: 1, + activeFindings: [ + makeRuntimeFinding({ title: 'Some custom runtime error' }), + ], + }).description, + ).toContain('Some custom runtime error'); + }); +}); + +describe('getFindingAssessmentDescription — multi-finding joinAssessmentParts', () => { + it('joins two summary parts with "and"', () => { + expect( + getPatrolAssessmentPresentation({ + warningFindings: 2, + activeFindings: [ + makeFinding({ severity: 'critical', resourceId: 'vm-1', title: 'Host down' }), + makeFinding({ severity: 'warning', resourceId: 'vm-2', title: 'Disk full' }), + ], + }).description, + ).toBe( + 'Patrol surfaced 1 active critical finding in your infrastructure and 1 active warning finding in your infrastructure. Review the active findings for more detail.', + ); + }); + + it('joins four summary parts with an Oxford comma', () => { + expect( + getPatrolAssessmentPresentation({ + criticalFindings: 2, + activeFindings: [ + makeFinding({ severity: 'critical', resourceId: 'vm-1', title: 'Host down' }), + makeFinding({ severity: 'warning', resourceId: 'vm-2', title: 'Disk full' }), + makeRuntimeFinding({ severity: 'critical' }), + makeRuntimeFinding({ + severity: 'warning', + title: 'Pulse Patrol: Slow response', + }), + ], + }).description, + ).toBe( + 'Patrol surfaced 1 active critical finding in your infrastructure, 1 active warning finding in your infrastructure, 1 active critical Patrol runtime issue, and 1 active Patrol runtime issue. Review the active findings for more detail.', + ); + }); + + it('uses the criticalFindings fallback when no active findings classify', () => { + expect( + getPatrolAssessmentPresentation({ + criticalFindings: 3, + activeFindings: [makeFinding({ status: 'resolved', severity: 'critical' })], + }).description, + ).toBe( + 'Patrol surfaced 3 active critical findings. Review the active findings for more detail.', + ); + }); + + it('uses the warningFindings fallback when no active findings classify and no criticals exist', () => { + expect( + getPatrolAssessmentPresentation({ + warningFindings: 2, + }).description, + ).toBe( + 'Patrol surfaced 2 active warning findings. Review the active findings for more detail.', + ); + }); +}); + +describe('predictionReadsAsAllClear (via getFindingAssessmentDescription)', () => { + it.each([ + ['healthy with no significant issue'], + ['no significant issues detected'], + ['no active issues'], + ['no issues detected'], + ['all clear'], + ['ALL CLEAR'], // case-insensitive + ])('suppresses the all-clear prediction "%s" for a single runtime issue', (prediction) => { + const description = getPatrolAssessmentPresentation({ + warningFindings: 1, + overallHealth: makeHealth({ score: 95, grade: 'A', prediction }), + activeFindings: [makeRuntimeFinding({ severity: 'warning' })], + }).description; + expect(description).not.toBe(prediction); + expect(description).toContain('Patrol has an active runtime issue'); + }); + + it('returns false for an empty prediction so the runtime summary is used', () => { + const description = getPatrolAssessmentPresentation({ + warningFindings: 1, + overallHealth: makeHealth({ score: 95, grade: 'A', prediction: '' }), + activeFindings: [makeRuntimeFinding({ severity: 'warning' })], + }).description; + expect(description).toContain('Review the Patrol runtime issue for more detail.'); + }); +}); + +describe('predictionReadsAsCoverageGap (via getPatrolAssessmentPresentation)', () => { + it.each([ + ['coverage is incomplete'], + ['coverage incomplete'], + ['not fully verified'], + ['limited to scoped runs'], + ['limited to targeted'], + ['runs encountered errors'], + ['ended with errors'], + ['current issue list'], + ['current full issue list'], + ['current health may be incomplete'], + ['summary may be incomplete'], + ])( + 'suppresses the coverage-gap prediction "%s" after a verified full run', + (prediction) => { + const description = getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 80, + grade: 'B', + factors: [coverageFactor()], + prediction, + }), + runs: [successfulFullRun()], + }).description; + expect(description).toBe('Patrol still needs attention.'); + }, + ); +}); + +// =========================================================================== +// hasSuccessfulFullCoverageRun (private) — exercised through +// getPatrolAssessmentPresentation's coverage-gap branch. +// =========================================================================== + +describe('hasSuccessfulFullCoverageRun (via coverage-gap branch)', () => { + it('treats a full run with errors as not successful so the coverage gap stays', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 70, + grade: 'C', + factors: [coverageFactor()], + prediction: 'Coverage is incomplete.', + }), + runs: [ + makeRun({ + type: 'patrol', + resources_checked: 50, + error_count: 3, + status: 'error', + }), + ], + }), + ).toMatchObject({ title: 'Coverage incomplete' }); + }); + + it('treats a full run with zero resources as not successful so the coverage gap stays', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 70, + grade: 'C', + factors: [coverageFactor()], + prediction: 'Coverage is incomplete.', + }), + runs: [makeRun({ type: 'patrol', resources_checked: 0, error_count: 0 })], + }), + ).toMatchObject({ title: 'Coverage incomplete' }); + }); + + it('treats a scoped-only run as not a full coverage run', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 70, + grade: 'C', + factors: [coverageFactor()], + prediction: 'Coverage is incomplete.', + }), + runs: [makeRun({ type: 'scoped', resources_checked: 5, error_count: 0 })], + }), + ).toMatchObject({ title: 'Coverage incomplete' }); + }); + + it('clears the coverage gap after a successful full run', () => { + expect( + getPatrolAssessmentPresentation({ + overallHealth: makeHealth({ + score: 85, + grade: 'B', + factors: [coverageFactor()], + prediction: 'A few warnings were detected.', + }), + runs: [successfulFullRun(60)], + }), + ).toMatchObject({ title: 'Health requires attention' }); + }); +}); + +// =========================================================================== +// getPatrolVerificationPresentation — runtime-state branches, full-run-with- +// errors, zero-resource full run, limited-run variants (verification/scoped/ +// unknown), no-completed-runs, and getVerificationActivityMixLabel undefined +// paths. +// =========================================================================== + +describe('getPatrolVerificationPresentation — runtime state', () => { + it('maps the blocked state to the paused runtime presentation', () => { + expect(getPatrolVerificationPresentation({ runtimeState: 'blocked' })).toEqual({ + title: 'Patrol paused', + description: + 'Patrol cannot check infrastructure until the blocking condition is cleared.', + compactLabel: 'Patrol paused', + tone: 'warning', + }); + }); + + it('maps the disabled state to the disabled runtime presentation', () => { + expect(getPatrolVerificationPresentation({ runtimeState: 'disabled' })).toEqual({ + title: 'Patrol disabled', + description: 'Enable Patrol to resume checks.', + compactLabel: 'Patrol disabled', + tone: 'info', + }); + }); + + it('maps the unavailable state to the unavailable runtime presentation', () => { + expect(getPatrolVerificationPresentation({ runtimeState: 'unavailable' })).toEqual({ + title: 'Patrol unavailable', + description: + 'Patrol is not ready yet. Check Provider & Models and runtime availability.', + compactLabel: 'Patrol unavailable', + tone: 'error', + }); + }); +}); + +describe('getPatrolVerificationPresentation — full run with errors (hasRunErrors)', () => { + it('reports a needs-review check when the full run has errors and covered resources', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ + type: 'patrol', + resources_checked: 10, + error_count: 2, + status: 'error', + }), + ], + }), + ).toEqual({ + title: 'Patrol check needs review', + description: + 'The most recent Patrol check covered 10 resources but ended with 2 errors.', + compactLabel: 'Check needs review', + tone: 'warning', + lastFullRunAt: '2026-07-10T09:05:00Z', + }); + }); + + it('reports a needs-review check with a generic message when errors occurred and zero resources', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ + type: 'patrol', + resources_checked: 0, + error_count: 1, + status: 'error', + }), + ], + }), + ).toEqual({ + title: 'Patrol check needs review', + description: 'The most recent Patrol check ended with errors.', + compactLabel: 'Check needs review', + tone: 'warning', + lastFullRunAt: '2026-07-10T09:05:00Z', + }); + }); + + it('detects errors via status "error" even when error_count is 0', () => { + const result = getPatrolVerificationPresentation({ + runs: [ + makeRun({ + type: 'patrol', + resources_checked: 5, + error_count: 0, + status: 'error', + }), + ], + }); + expect(result.title).toBe('Patrol check needs review'); + }); + + it('detects errors case-insensitively via status "ERROR" as wrong-typed input', () => { + const run = makeRun({ + type: 'patrol', + resources_checked: 5, + error_count: 0, + status: 'ERROR' as unknown as PatrolRunRecord['status'], + }); + expect(getPatrolVerificationPresentation({ runs: [run] }).title).toBe( + 'Patrol check needs review', + ); + }); +}); + +describe('getPatrolVerificationPresentation — successful full run', () => { + it('reports a successful check with zero resources', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ + type: 'patrol', + resources_checked: 0, + error_count: 0, + status: 'healthy', + }), + ], + }), + ).toEqual({ + title: 'Recently checked', + description: 'The most recent Patrol check completed successfully.', + compactLabel: 'Recently checked', + tone: 'success', + lastFullRunAt: '2026-07-10T09:05:00Z', + }); + }); + + it('reports a successful check with a single resource (singular)', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ + type: 'patrol', + resources_checked: 1, + error_count: 0, + status: 'healthy', + }), + ], + }).description, + ).toBe('The most recent Patrol check completed successfully and covered 1 resource.'); + }); +}); + +describe('getPatrolVerificationPresentation — limited runs', () => { + it('reports follow-up checks with zero resources', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ + type: 'verification', + resources_checked: 0, + error_count: 0, + status: 'healthy', + }), + ], + }).description, + ).toBe( + 'Recent follow-up checks did not cover your full infrastructure. Run Patrol to check everything.', + ); + }); + + it('reports targeted checks with zero resources', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ + type: 'scoped', + resources_checked: 0, + error_count: 0, + status: 'healthy', + }), + ], + }).description, + ).toBe( + 'Recent targeted checks did not cover your full infrastructure. Run Patrol to check everything.', + ); + }); + + it('reports an unknown-type limited run with resources using the targeted fallback', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ + type: 'custom', + resources_checked: 3, + error_count: 0, + status: 'healthy', + }), + ], + }).description, + ).toBe('Recent targeted checks covered 3 resources. Run Patrol to check everything.'); + }); + + it('uses the default limited description for an unknown type with zero resources', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ + type: 'custom', + resources_checked: 0, + error_count: 0, + status: 'healthy', + }), + ], + }).description, + ).toBe( + 'Recent activity only checked part of your infrastructure. Run Patrol to check everything.', + ); + }); +}); + +describe('getPatrolVerificationPresentation — no completed runs', () => { + it('reports a pending check when no runs exist', () => { + expect(getPatrolVerificationPresentation({})).toEqual({ + title: 'Run Patrol to check', + description: 'Patrol has not completed a check yet.', + compactLabel: 'Check pending', + tone: 'info', + }); + }); + + it('reports a pending check when the only run is not completed', () => { + expect( + getPatrolVerificationPresentation({ + runs: [makeRun({ completed_at: '' })], + }), + ).toEqual({ + title: 'Run Patrol to check', + description: 'Patrol has not completed a check yet.', + compactLabel: 'Check pending', + tone: 'info', + }); + }); +}); + +describe('getVerificationActivityMixLabel (via getPatrolVerificationPresentation)', () => { + it('omits activityMixLabel when there is only a single completed run', () => { + const result = getPatrolVerificationPresentation({ + runs: [successfulFullRun(10)], + }); + expect(result.activityMixLabel).toBeUndefined(); + }); + + it('omits activityMixLabel when all completed runs are full patrols', () => { + const result = getPatrolVerificationPresentation({ + runs: [ + makeRun({ + id: 'run-a', + started_at: '2026-07-10T10:00:00Z', + completed_at: '2026-07-10T10:05:00Z', + type: 'patrol', + resources_checked: 40, + }), + makeRun({ + id: 'run-b', + started_at: '2026-07-10T09:00:00Z', + completed_at: '2026-07-10T09:05:00Z', + type: 'full', + resources_checked: 40, + }), + ], + }); + expect(result.activityMixLabel).toBeUndefined(); + }); +}); + +// =========================================================================== +// getPatrolRecencyPresentation — timestamp fallback logic branches, +// formatRecencyResourcesCheckedLabel singular verified case. +// =========================================================================== + +describe('getPatrolRecencyPresentation — timestamp fallback logic', () => { + it('prefers lastPatrolAt when lastActivityAt is an unparseable date', () => { + expect( + getPatrolRecencyPresentation({ + lastPatrolAt: '2026-07-10T09:57:00Z', + lastActivityAt: 'not-a-date', + }), + ).toEqual({ + label: 'Last check', + timestamp: '2026-07-10T09:57:00Z', + }); + }); + + it('prefers lastPatrolAt when it is newer than or equal to lastActivityAt', () => { + expect( + getPatrolRecencyPresentation({ + lastPatrolAt: '2026-07-10T10:00:00Z', + lastActivityAt: '2026-07-10T09:00:00Z', + }), + ).toEqual({ + label: 'Last check', + timestamp: '2026-07-10T10:00:00Z', + }); + }); + + it('prefers lastActivityAt when lastPatrolAt is an unparseable date', () => { + expect( + getPatrolRecencyPresentation({ + lastPatrolAt: 'not-a-date', + lastActivityAt: '2026-07-10T09:00:00Z', + }), + ).toEqual({ + label: 'Last activity', + timestamp: '2026-07-10T09:00:00Z', + }); + }); + + it('returns last activity when only lastActivityAt is provided', () => { + expect( + getPatrolRecencyPresentation({ lastActivityAt: '2026-07-10T09:00:00Z' }), + ).toEqual({ + label: 'Last activity', + timestamp: '2026-07-10T09:00:00Z', + }); + }); + + it('returns last check when only lastPatrolAt is provided', () => { + expect( + getPatrolRecencyPresentation({ lastPatrolAt: '2026-07-10T09:57:00Z' }), + ).toEqual({ + label: 'Last check', + timestamp: '2026-07-10T09:57:00Z', + }); + }); + + it('returns a timestamp-less last-activity label when nothing is provided', () => { + expect(getPatrolRecencyPresentation({})).toEqual({ + label: 'Last activity', + }); + }); + + it('trims whitespace from timestamps before comparing', () => { + expect( + getPatrolRecencyPresentation({ + lastPatrolAt: ' 2026-07-10T09:57:00Z ', + }), + ).toEqual({ + label: 'Last check', + timestamp: '2026-07-10T09:57:00Z', + }); + }); +}); + +describe('getPatrolRecencyPresentation — resourcesCheckedLabel', () => { + it('uses "verified 1 resource" (singular) for a successful full run with one resource', () => { + expect( + getPatrolRecencyPresentation({ + runs: [ + makeRun({ + type: 'patrol', + resources_checked: 1, + error_count: 0, + status: 'healthy', + }), + ], + }), + ).toEqual({ + label: 'Last check', + timestamp: '2026-07-10T09:05:00Z', + resourcesChecked: 1, + resourcesCheckedLabel: 'verified 1 resource', + }); + }); + + it('uses "checked" for a scoped run regardless of resources', () => { + expect( + getPatrolRecencyPresentation({ + runs: [ + makeRun({ + type: 'scoped', + resources_checked: 3, + error_count: 0, + status: 'healthy', + }), + ], + }).resourcesCheckedLabel, + ).toBe('checked 3 resources'); + }); + + it('omits resourcesCheckedLabel when the completed run checked zero resources', () => { + const result = getPatrolRecencyPresentation({ + runs: [ + makeRun({ + type: 'patrol', + resources_checked: 0, + error_count: 0, + status: 'healthy', + }), + ], + }); + expect(result.resourcesChecked).toBeUndefined(); + expect(result.resourcesCheckedLabel).toBeUndefined(); + }); + + it('uses "checked" for a full run that ended with errors', () => { + expect( + getPatrolRecencyPresentation({ + runs: [ + makeRun({ + type: 'patrol', + resources_checked: 5, + error_count: 1, + status: 'error', + }), + ], + }).resourcesCheckedLabel, + ).toBe('checked 5 resources'); + }); +}); + +// =========================================================================== +// normalizeRunType (private) — exercised through run-type classification in +// verification and recency presentations. Covers case/whitespace/empty +// normalization and the resulting full/scoped/verification classification. +// =========================================================================== + +describe('normalizeRunType (via run-type classification)', () => { + it('treats an empty type as a full run', () => { + expect( + getPatrolVerificationPresentation({ + runs: [makeRun({ type: '', resources_checked: 5, error_count: 0 })], + }).title, + ).toBe('Recently checked'); + }); + + it('treats undefined type as a full run', () => { + const run = makeRun({ resources_checked: 5, error_count: 0 }); + delete (run as Partial).type; + expect( + getPatrolVerificationPresentation({ runs: [run] }).title, + ).toBe('Recently checked'); + }); + + it('treats "PATROL" (uppercase) as a full run', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ type: 'PATROL', resources_checked: 5, error_count: 0 }), + ], + }).title, + ).toBe('Recently checked'); + }); + + it('treats " Full " (whitespace, mixed case) as a full run', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ type: ' Full ', resources_checked: 5, error_count: 0 }), + ], + }).title, + ).toBe('Recently checked'); + }); + + it('classifies "SCOPED" (uppercase) as a scoped run', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ type: 'SCOPED', resources_checked: 1, error_count: 0 }), + ], + }).description, + ).toContain('targeted checks'); + }); + + it('classifies "verification" as a verification run', () => { + expect( + getPatrolVerificationPresentation({ + runs: [ + makeRun({ type: 'verification', resources_checked: 1, error_count: 0 }), + ], + }).description, + ).toContain('follow-up checks'); + }); +});