diff --git a/platform/main/PlatformApp.test.tsx b/platform/main/PlatformApp.test.tsx index e41e02e532..fa402c601d 100644 --- a/platform/main/PlatformApp.test.tsx +++ b/platform/main/PlatformApp.test.tsx @@ -6,6 +6,7 @@ import { MemoryRouter } from 'react-router-dom'; import { SWRConfig } from 'swr'; import { beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest'; import * as GatewayUIAuth from './GatewayUIAuth'; +import * as PlatformActiveUserModule from './PlatformActiveUserProvider'; import { PlatformApp } from './PlatformApp'; // Mock fetch globally @@ -42,6 +43,7 @@ interface ManagedCloudStub { describe('Platform Subscription and Session Validation Tests', () => { let useIsManagedCloudStub: MockInstance & ManagedCloudStub; + let usePlatformActiveUserStub: MockInstance; let fetchMock: MockInstance; beforeAll(async () => { @@ -73,6 +75,11 @@ describe('Platform Subscription and Session Validation Tests', () => { return useIsManagedCloudStub; }; + // Default to superuser so existing tests see banners + usePlatformActiveUserStub = vi + .spyOn(PlatformActiveUserModule, 'usePlatformActiveUser') + .mockReturnValue({ activePlatformUser: { is_superuser: true } as never }); + // Default intercepts matching the original Cypress test fetchMock.mockImplementation((url: string | URL) => { const urlString = url.toString(); @@ -278,6 +285,145 @@ describe('Platform Subscription and Session Validation Tests', () => { expect(screen.queryByRole('banner')).not.toBeInTheDocument(); }); }); + + it('should not display compliance banners for non-admin users', async () => { + // Arrange: Set user as non-superuser + usePlatformActiveUserStub.mockReturnValue({ + activePlatformUser: { is_superuser: false }, + }); + + // Override the config intercept to return non-compliant license + fetchMock.mockImplementation((url: string | URL) => { + const urlString = url.toString(); + + if ( + urlString.includes('/config/') && + (urlString.includes('/api/controller/v2/') || urlString.includes('/api/v2/')) + ) { + return Promise.resolve({ + ok: true, + json: () => + Promise.resolve({ + license_info: { + compliant: false, + grace_period_remaining: 2 * 24 * 60 * 60, + }, + }), + } as Response); + } + + if (urlString.includes('/api/gateway/v1/session/')) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve({ expires_in_seconds: 3600 }), + } as Response); + } + + return Promise.reject(new Error(`Unmocked URL: ${urlString}`)); + }); + + // Act + mountPlatformApp(); + + // Assert: No compliance banner should be shown to non-admin users + await waitFor(() => { + expect( + screen.queryByText(/Your subscription is out of compliance/) + ).not.toBeInTheDocument(); + }); + }); + + it('should display compliance banners only for admin users', async () => { + // Arrange: Set user as superuser + usePlatformActiveUserStub.mockReturnValue({ + activePlatformUser: { is_superuser: true }, + }); + + // Override the config intercept to return non-compliant license + fetchMock.mockImplementation((url: string | URL) => { + const urlString = url.toString(); + + if ( + urlString.includes('/config/') && + (urlString.includes('/api/controller/v2/') || urlString.includes('/api/v2/')) + ) { + return Promise.resolve({ + ok: true, + json: () => + Promise.resolve({ + license_info: { + compliant: false, + grace_period_remaining: 2 * 24 * 60 * 60, + }, + }), + } as Response); + } + + if (urlString.includes('/api/gateway/v1/session/')) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve({ expires_in_seconds: 3600 }), + } as Response); + } + + return Promise.reject(new Error(`Unmocked URL: ${urlString}`)); + }); + + // Act + mountPlatformApp(); + + // Assert: Compliance banner should be shown to admin users + await waitFor(() => { + expect( + screen.getByText('Your subscription is out of compliance. 2 days grace period remaining.') + ).toBeInTheDocument(); + }); + }); + + it('should not display subscription expiry banners for non-admin users', async () => { + // Arrange: Set user as non-superuser + usePlatformActiveUserStub.mockReturnValue({ + activePlatformUser: { is_superuser: false }, + }); + + // Override the config intercept to return expiring subscription + fetchMock.mockImplementation((url: string | URL) => { + const urlString = url.toString(); + + if ( + urlString.includes('/config/') && + (urlString.includes('/api/controller/v2/') || urlString.includes('/api/v2/')) + ) { + return Promise.resolve({ + ok: true, + json: () => + Promise.resolve({ + license_info: { + compliant: true, + time_remaining: 14 * 24 * 60 * 60, + }, + }), + } as Response); + } + + if (urlString.includes('/api/gateway/v1/session/')) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve({ expires_in_seconds: 3600 }), + } as Response); + } + + return Promise.reject(new Error(`Unmocked URL: ${urlString}`)); + }); + + // Act + mountPlatformApp(); + + // Assert: No expiry banner should be shown to non-admin users + await waitFor(() => { + expect(screen.queryByText(/Your subscription will expire/)).not.toBeInTheDocument(); + }); + }); }); describe('Controller Down Banner', () => { diff --git a/platform/main/PlatformApp.tsx b/platform/main/PlatformApp.tsx index e3295701cb..61358c00bc 100644 --- a/platform/main/PlatformApp.tsx +++ b/platform/main/PlatformApp.tsx @@ -11,6 +11,7 @@ import { useUIFlag } from '../settings/ui-flags/useUIFlag'; import { gatewayAPI } from '../utils/gateway-api-utils'; import { useIsManagedCloudInstall } from './GatewayUIAuth'; import { PersonaViewSwitcher } from './persona-view/PersonaViewSwitcher'; +import { usePlatformActiveUser } from './PlatformActiveUserProvider'; import { PlatformMasthead } from './PlatformMasthead'; import { usePlatformNavigation } from './usePlatformNavigation'; import { PageTitleProvider } from '@ansible/ansible-ui-framework/PageTitle/PageTitle'; @@ -68,7 +69,10 @@ export function PlatformApp() { const { awxConfig, serviceDown, serviceDownStatusCode } = useAwxConfigState(); const managedCloudInstall = useIsManagedCloudInstall() ?? false; + const { activePlatformUser } = usePlatformActiveUser(); const subscriptionBanner = useMemo(() => { + // Hide compliance banners for non-admin users - they cannot act on compliance issues + if (!activePlatformUser?.is_superuser) return null; if (!awxConfig?.license_info || managedCloudInstall) return null; if (!awxConfig.license_info.compliant) { if (awxConfig.license_info.grace_period_remaining) { @@ -109,7 +113,7 @@ export function PlatformApp() { ); } return null; - }, [awxConfig, managedCloudInstall]); + }, [activePlatformUser?.is_superuser, awxConfig, managedCloudInstall]); const controllerDownBanner = useMemo(() => { if (!serviceDown) return null;