diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java index 7c9b28a1..8a09ed73 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java @@ -297,6 +297,13 @@ class DocumentControllerTest { .andExpect(status().isForbidden()); } + @Test + @WithMockUser(authorities = "READ_ALL") + void createDocument_returns403_forReaderOnly() throws Exception { + mockMvc.perform(multipart("/api/documents").with(csrf())) + .andExpect(status().isForbidden()); + } + @Test @WithMockUser(authorities = "WRITE_ALL") void createDocument_returns200_whenHasWritePermission() throws Exception { @@ -414,6 +421,13 @@ class DocumentControllerTest { .andExpect(status().isForbidden()); } + @Test + @WithMockUser(authorities = "READ_ALL") + void quickUpload_returns403_forReaderOnly() throws Exception { + mockMvc.perform(multipart("/api/documents/quick-upload").with(csrf())) + .andExpect(status().isForbidden()); + } + @Test @WithMockUser(authorities = "WRITE_ALL") void quickUpload_returns200_withValidPdfFile() throws Exception { diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 2703ff9c..136eb060 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -43,6 +43,8 @@ const isAdmin = $derived( data?.user?.groups?.some((g: { permissions: string[] }) => g.permissions.includes('ADMIN')) ); +const canUpload = $derived(Boolean(data?.user && data.canWrite)); + // Set after client-side hydration completes. Used by E2E tests to know the // page is interactive (event handlers registered) before they interact with it. let hydrated = $state(false); @@ -75,7 +77,7 @@ const userInitials = $derived.by(() => {
- {#if data?.user} + {#if canUpload} { const link = page.getByRole('link', { name: /Hochladen|Upload|Subir/i }); await expect.element(link).toHaveAttribute('href', '/documents/new'); }); + + it('is hidden for a user without WRITE_ALL', async () => { + render(Layout, { data: makeData({ canWrite: false }), children: emptySnippet }); + await expect + .element(page.getByRole('link', { name: /Hochladen|Upload|Subir/i })) + .not.toBeInTheDocument(); + }); + + it('is hidden for an ANNOTATE_ALL-only user (gate is lack of WRITE_ALL, not READ_ALL)', async () => { + render(Layout, { + data: makeData({ canWrite: false, canAnnotate: true }), + children: emptySnippet + }); + await expect + .element(page.getByRole('link', { name: /Hochladen|Upload|Subir/i })) + .not.toBeInTheDocument(); + }); }); // ─── Dropdown ─────────────────────────────────────────────────────────────────