feat: roles SPA foundations

This commit is contained in:
Florian Sylvain committed 2026-10-27 22:21:12 +01:00
1 parent 80f7037d3a
commit 907949aa12
180 files changed
+4308 -835

No files matched your search

+26 -5
View File
@@ -10,6 +10,8 @@ import type { CacheStats } from '@/composables/useVariantCache'
import { initializeLocale } from '@/i18n'
import { useSessionStore } from '@/stores/session'
import CachePage from '../CachePage.vue'
import { accessFor, customAccess } from '@/test/access'
import type { Access } from '@/lib/permissions'
vi.mock('@/composables/useApi', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/composables/useApi')>()
@@ -111,6 +113,7 @@ async function mountPage(
options: {
desktop?: boolean
role?: string | null
access?: Access
routes?: Record<string, Handler>
} = {},
) {
@@ -124,7 +127,7 @@ async function mountPage(
username: 'marta',
id: 1,
displayName: 'Marta Alves',
role: sessionRole as 'owner' | 'admin' | undefined,
access: options.access ?? accessFor(sessionRole),
}
session.status = 'ready'
requestMock = vi.fn((path: string, init?: RequestInit) => {
@@ -142,7 +145,7 @@ async function mountPage(
verified: true,
username: 'marta',
id: 1,
role: sessionRole,
access: accessFor(sessionRole),
}),
request: requestMock,
})
@@ -263,11 +266,11 @@ describe('Rule: Cache screen, access and header', () => {
routes: role ? { [STATS]: () => Promise.reject(new ApiError(403)) } : {},
})
expect(document.body.textContent).toContain("You don't have access")
expect(document.body.textContent).toContain("You don't have access to this page")
expect(document.body.textContent).toContain(
'Only admins can manage image variants. Ask an admin if you need one changed.',
"doesn't include it. Ask an owner or admin if you need it.",
)
expect(findButton('Back to Home')).toBeDefined()
expect(findButton('Go to Home') ?? findButton('Go to Articles')).toBeDefined()
expect(document.body.querySelector('[role="tablist"]')).toBeNull()
expect(document.body.querySelector('h1:not([data-slot="title"])')).toBeNull()
expect(calls(STATS)).toHaveLength(role ? 1 : 0)
@@ -821,3 +824,21 @@ describe('Rule: Accessibility and vocabulary', () => {
}
})
})
describe('Rule: Cache screen, settings.edit_general', () => {
it('lets a session with settings.edit_general purge and shows no read-only banner', async () => {
await mountPage({ access: customAccess('settings.view, settings.edit_general') })
expect(document.body.textContent).not.toContain(
'You can read these settings but not change them',
)
expect(findButton('Purge all variants')?.disabled).toBe(false)
})
it('locks the purge controls behind the banner with settings.view alone', async () => {
await mountPage({ access: customAccess('settings.view') })
expect(document.body.textContent).toContain('You can read these settings but not change them')
expect(findButton('Purge all variants')?.disabled).toBe(true)
})
})