From c0ded1f4823dd0bb3375eabfe0fa0f29c1fe3fb1 Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Tue, 5 May 2026 14:26:03 +0200 Subject: [PATCH] feat(ST-nma6ylbl): requireAdmin() guard + /admin layout-shell + tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/auth-guard.ts: requireAdmin() toegevoegd — redirect /dashboard bij !userId of !isAdmin - app/(app)/admin/layout.tsx: admin-sidebar met links naar /admin/users, /admin/jobs, /admin/products - app/(app)/admin/page.tsx: redirect-stub naar /admin/users - __tests__/lib/auth-guard.test.ts: 3 tests voor requireAdmin() (geen userId, isAdmin=false, isAdmin=true) --- __tests__/lib/auth-guard.test.ts | 35 ++++++++++++++++++++++++++++++++ app/(app)/admin/layout.tsx | 16 +++++++++++++++ app/(app)/admin/page.tsx | 5 +++++ lib/auth-guard.ts | 8 ++++++++ 4 files changed, 64 insertions(+) create mode 100644 app/(app)/admin/layout.tsx create mode 100644 app/(app)/admin/page.tsx diff --git a/__tests__/lib/auth-guard.test.ts b/__tests__/lib/auth-guard.test.ts index b162921..552c1cb 100644 --- a/__tests__/lib/auth-guard.test.ts +++ b/__tests__/lib/auth-guard.test.ts @@ -8,6 +8,41 @@ vi.mock('@/lib/auth', () => ({ getSession: getSessionMock })) vi.mock('@/lib/auth/pairing', () => ({ isPairedSessionExpired: isPairedSessionExpiredMock })) vi.mock('next/navigation', () => ({ redirect: redirectMock })) +describe('requireAdmin', () => { + beforeEach(() => { + getSessionMock.mockReset() + isPairedSessionExpiredMock.mockReset() + redirectMock.mockClear() + }) + + afterEach(() => { + vi.resetModules() + }) + + it('redirect /dashboard als userId ontbreekt', async () => { + getSessionMock.mockResolvedValue({ userId: undefined, isAdmin: false }) + const { requireAdmin } = await import('@/lib/auth-guard') + await expect(requireAdmin()).rejects.toThrow('REDIRECT_CALLED') + expect(redirectMock).toHaveBeenCalledWith('/dashboard') + }) + + it('redirect /dashboard als isAdmin false is', async () => { + getSessionMock.mockResolvedValue({ userId: 'u1', isAdmin: false }) + const { requireAdmin } = await import('@/lib/auth-guard') + await expect(requireAdmin()).rejects.toThrow('REDIRECT_CALLED') + expect(redirectMock).toHaveBeenCalledWith('/dashboard') + }) + + it('geeft sessie terug als isAdmin true is', async () => { + const sess = { userId: 'u1', isAdmin: true } + getSessionMock.mockResolvedValue(sess) + const { requireAdmin } = await import('@/lib/auth-guard') + const result = await requireAdmin() + expect(result).toBe(sess) + expect(redirectMock).not.toHaveBeenCalled() + }) +}) + describe('requireSession', () => { beforeEach(() => { getSessionMock.mockReset() diff --git a/app/(app)/admin/layout.tsx b/app/(app)/admin/layout.tsx new file mode 100644 index 0000000..0d1d279 --- /dev/null +++ b/app/(app)/admin/layout.tsx @@ -0,0 +1,16 @@ +import { requireAdmin } from '@/lib/auth-guard' +import Link from 'next/link' + +export default async function AdminLayout({ children }: { children: React.ReactNode }) { + await requireAdmin() + return ( +