refac: DELETE /v1/user/{id}?expected_role_id=N, user.remove audit event, 409 target_role_not_found, GetAll returns its error, Own-scope visibility filter moved into repos, JSON tags removed from use-case types

This commit is contained in:
Florian Sylvain committed 2026-10-29 16:28:25 +01:00
1 parent 5d6f378fce
commit b39a41be88
124 files changed
+8816 -523

No files matched your search

+62 -17
View File
@@ -8,6 +8,7 @@ import { useSessionStore, type Session } from '@/stores/session'
import { ApiError, NetworkError, authRedirectPending, useApi } from '@/composables/useApi'
import { DESKTOP_WIDTH, MOBILE_WIDTH, setViewportWidth } from '@/components/__tests__/viewport'
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')>()
@@ -942,25 +943,67 @@ function roleRequests(request: ReturnType<typeof vi.fn>) {
return request.mock.calls.filter(([path]) => String(path).startsWith('/v1/role'))
}
function sessionWith(access: Access): Session {
return { ...MARTA, access }
}
function roleCard() {
return [...document.body.querySelectorAll('h2')]
.find((heading) => heading.textContent?.trim() === 'Your role')!
.closest('section')!
}
function roleBadges() {
return [...roleCard().querySelectorAll('span[data-slot="base"]')]
.map((badge) => badge.textContent?.trim())
.filter((text) => text)
}
describe('Profile: your role', () => {
it('shows the role card with the badge, the assigner and the keys sentence', async () => {
it.each([
['a custom role', contributor(MARTA_ASSIGNER), ['Contributor', 'Custom']],
[
'the locked Owner',
sessionWith({
role: { id: 1, name: 'Owner', built_in: true, locked: true },
permissions: customAccess('content.read=all').permissions,
}),
['owner', 'Locked', 'Built-in'],
],
[
'a built-in Writer',
sessionWith({
role: { id: 3, name: 'Writer', built_in: true, locked: false },
permissions: customAccess('content.read=all').permissions,
}),
['writer', 'Built-in'],
],
])('shows the badge and the tags for %s in one row', async (_label, session, expected) => {
await mountApp({ session })
expect(roleBadges()).toEqual(expected)
expect(roleCard().textContent).toContain(KEYS_SENTENCE)
expect(buttonByText(/^See what I can do$/)).toBeDefined()
expect(roleCard().querySelector('[class*="i-lucide-list-checks"], svg')).not.toBeNull()
})
it('hides Locked when not locked and Custom when built in', async () => {
await mountApp({ session: contributor(MARTA_ASSIGNER) })
expect(pageText()).toContain('Your role')
expect(pageText()).toContain('Contributor')
expect(pageText()).toContain('Assigned by Marta Alves')
expect(pageText()).toContain(KEYS_SENTENCE)
expect(buttonByText(/^See what I can do$/)).toBeDefined()
expect(roleCard().textContent).not.toContain('Locked')
expect(roleCard().textContent).not.toContain('Built-in')
})
it.each([
['an object', MARTA_ASSIGNER],
['null', null],
['an absent key', undefined],
])('renders no Assigned by text for %s', async (_label, assigner) => {
])('shows no summary line and no Assigned by text with %s', async (_label, assigner) => {
await mountApp({ session: contributor(assigner) })
expect(pageText()).toContain('Your role')
expect(pageText()).not.toContain('Assigned by')
expect(pageText()).not.toContain('Creates content and edits only their own.')
})
it('places the card after the Account card', async () => {
@@ -970,21 +1013,23 @@ describe('Profile: your role', () => {
expect(titles.indexOf('Your role')).toBe(titles.indexOf('Account') + 1)
})
it('follows a refreshed session for the assigner and the role', async () => {
it('follows a refreshed session for the badge, the tags and the summary', async () => {
await mountApp({ session: contributor(MARTA_ASSIGNER) })
expect(roleBadges()).toEqual(['Contributor', 'Custom'])
useSessionStore().session = {
...contributor({ id: 2, name: 'Jonas Weber' }),
const writer: Access = {
role: { id: 3, name: 'Writer', built_in: true, locked: false },
permissions: customAccess('content.read=all').permissions,
}
await flushPromises()
expect(pageText()).toContain('Assigned by Jonas Weber')
expect(pageText()).not.toContain('Assigned by Marta Alves')
const writer = customAccess('content.read=all', 'Writer')
useSessionStore().session = { ...MARTA, access: writer }
await flushPromises()
expect(pageText()).toContain('Writer')
expect(pageText()).not.toContain('Assigned by')
expect(roleBadges()).toEqual(['writer', 'Built-in'])
buttonByText(/^See what I can do$/)!.click()
await flushPromises()
expect(document.body.querySelector('[role="dialog"]')?.textContent).toContain(
'A summary of your role, Writer. Read-only.',
)
})
it('opens the summary dialog from the session grants without reading roles, and Close returns focus', async () => {