From 0947a37f014b06295cb87b91cec20d88dbf038b1 Mon Sep 17 00:00:00 2001 From: Faisal Misbah Date: Sat, 27 Jun 2026 06:13:13 +0500 Subject: [PATCH] fix(web): update protected route layout for security agent and usage pages --- .../src/app/(app)/protected-routes.test.ts | 76 +++++++++++++++++++ .../src/app/(app)/security-agent/layout.tsx | 5 +- apps/web/src/app/(app)/usage/page.tsx | 5 +- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/app/(app)/protected-routes.test.ts diff --git a/apps/web/src/app/(app)/protected-routes.test.ts b/apps/web/src/app/(app)/protected-routes.test.ts new file mode 100644 index 0000000000..0e71b84600 --- /dev/null +++ b/apps/web/src/app/(app)/protected-routes.test.ts @@ -0,0 +1,76 @@ +import React, { type ReactNode } from 'react'; +import type { User } from '@kilocode/db/schema'; +import { defineTestUser } from '@/tests/helpers/user.helper'; + +const mockGetUserFromAuthOrRedirect = jest.fn, []>(); + +(globalThis as typeof globalThis & { React: typeof React }).React = React; + +jest.mock('@/lib/user/server', () => ({ + getUserFromAuthOrRedirect: () => mockGetUserFromAuthOrRedirect(), +})); + +jest.mock('@/components/usage-analytics/UsageAnalyticsDashboard', () => ({ + UsageAnalyticsDashboard: () => null, +})); + +jest.mock('@/components/security-agent/SecurityAgentContext', () => ({ + SecurityAgentProvider: ({ children }: { children: ReactNode }) => children, +})); + +jest.mock('@/components/security-agent/SecurityAgentLayout', () => ({ + SecurityAgentLayout: ({ children }: { children: ReactNode }) => children, +})); + +type ProtectedRouteEntry = { + route: string; + render: () => Promise; +}; + +async function renderSecurityAgentLayout(): Promise { + const { default: SecurityAgentRootLayout } = await import('@/app/(app)/security-agent/layout'); + return SecurityAgentRootLayout({ children: 'security agent content' }); +} + +const protectedRouteEntries: ProtectedRouteEntry[] = [ + { + route: '/usage', + render: async () => { + const { default: UsagePage } = await import('@/app/(app)/usage/page'); + return UsagePage(); + }, + }, + { + route: '/security-agent/config', + render: renderSecurityAgentLayout, + }, + { + route: '/security-agent/audit-report', + render: renderSecurityAgentLayout, + }, +]; + +describe('protected app routes', () => { + const redirectSentinel = new Error('NEXT_REDIRECT'); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it.each(protectedRouteEntries)('checks authentication before rendering $route', async entry => { + mockGetUserFromAuthOrRedirect.mockResolvedValue(defineTestUser()); + + await expect(entry.render()).resolves.toBeDefined(); + expect(mockGetUserFromAuthOrRedirect).toHaveBeenCalledTimes(1); + }); + + it.each(protectedRouteEntries)( + 'propagates the unauthenticated redirect for $route', + async entry => { + mockGetUserFromAuthOrRedirect.mockRejectedValue(redirectSentinel); + + await expect(entry.render()).rejects.toBe(redirectSentinel); + expect(mockGetUserFromAuthOrRedirect).toHaveBeenCalledTimes(1); + } + ); +}); diff --git a/apps/web/src/app/(app)/security-agent/layout.tsx b/apps/web/src/app/(app)/security-agent/layout.tsx index 1e8336b454..d90cb64f99 100644 --- a/apps/web/src/app/(app)/security-agent/layout.tsx +++ b/apps/web/src/app/(app)/security-agent/layout.tsx @@ -1,12 +1,15 @@ import { SecurityAgentLayout } from '@/components/security-agent/SecurityAgentLayout'; import { SecurityAgentProvider } from '@/components/security-agent/SecurityAgentContext'; +import { getUserFromAuthOrRedirect } from '@/lib/user/server'; export const metadata = { title: 'Security Agent | Kilo Code', description: 'Monitor and manage Security Findings synced from Dependabot', }; -export default function SecurityAgentRootLayout({ children }: { children: React.ReactNode }) { +export default async function SecurityAgentRootLayout({ children }: { children: React.ReactNode }) { + await getUserFromAuthOrRedirect(); + return ( {children} diff --git a/apps/web/src/app/(app)/usage/page.tsx b/apps/web/src/app/(app)/usage/page.tsx index 9589fc43c6..1aecafae97 100644 --- a/apps/web/src/app/(app)/usage/page.tsx +++ b/apps/web/src/app/(app)/usage/page.tsx @@ -1,7 +1,10 @@ import { Suspense } from 'react'; import { UsageAnalyticsDashboard } from '@/components/usage-analytics/UsageAnalyticsDashboard'; +import { getUserFromAuthOrRedirect } from '@/lib/user/server'; + +export default async function UsagePage() { + await getUserFromAuthOrRedirect(); -export default function UsagePage() { return (