Files
RenewCMS/web/src/router/__tests__/routeAccess.spec.ts
T

148 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, describe, expect, it } from 'vitest'
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { h } from 'vue'
import uiPlugin from '@nuxt/ui/vue-plugin'
import UApp from '@nuxt/ui/components/App.vue'
import router from '@/router'
import RouteAccessGate from '@/components/RouteAccessGate.vue'
import { useSessionStore, type SessionStatus } from '@/stores/session'
import { customAccess, noAccess } from '@/test/access'
import { setLocale } from '@/i18n'
let wrapper: VueWrapper | null = null
interface Setup {
grants: string
role?: string
path: string
status?: SessionStatus
verified?: boolean
}
async function open({
grants,
role = 'Contributor',
path,
status = 'ready',
verified = true,
}: Setup) {
setActivePinia(createPinia())
const session = useSessionStore()
session.session = { id: 4, verified, displayName: '', access: customAccess(grants, role) }
session.status = status
await router.push(path).catch(() => undefined)
wrapper = mount(
() =>
h(UApp, null, () =>
h(RouteAccessGate, null, () => h('div', { 'data-testid': 'page' }, 'PAGE')),
),
{ attachTo: document.body, global: { plugins: [router, uiPlugin] } },
)
await flushPromises()
return wrapper
}
afterEach(async () => {
wrapper?.unmount()
wrapper = null
setLocale('en')
document.body.innerHTML = ''
await router.push('/login')
})
const NO_ACCESS_TITLE = "You don't have access to this page"
describe('A direct link to a page the role lacks', () => {
it.each([
['content.read=all', 'Contributor', '/schema', 'Go to Articles'],
['content.read=all', 'Contributor', '/schema/new', 'Go to Articles'],
['content.read=all', 'Contributor', '/users', 'Go to Articles'],
['content.read=all', 'Contributor', '/settings/sites', 'Go to Articles'],
['content.read=all', 'Contributor', '/integration/api-keys', 'Go to Articles'],
[
'content.read=all, content.read:events=none',
'Contributor',
'/content/events',
'Go to Articles',
],
['media.view', 'Media', '/schema', 'Go to Home'],
])(
'%s opening %s shows the page-level 403 and keeps the URL',
async (grants, role, path, action) => {
const view = await open({ grants, role, path })
expect(router.currentRoute.value.fullPath).toBe(path)
expect(view.find('[data-testid="page"]').exists()).toBe(false)
expect(view.text()).toContain(NO_ACCESS_TITLE)
expect(view.text()).toContain(
`Your role, ${role}, doesn't include it. Ask an owner or admin if you need it.`,
)
const buttons = view
.findAll('[data-slot="root"] button')
.filter((button) => !button.element.closest('nav'))
expect(buttons.map((button) => button.text()).filter((label) => label !== '')).toEqual([
action,
])
},
)
})
describe('A page the role can use', () => {
it.each([
['schema.view', '/schema'],
['schema.edit', '/schema/new'],
['users.view', '/users'],
['settings.manage_sites', '/settings/sites'],
['content.read:events=own', '/content/events'],
['content.create:events=all', '/content/events/new'],
['apikeys.view=own', '/integration/api-keys'],
['settings.view', '/settings/media/cache'],
])('%s opens %s without a 403', async (grants, path) => {
const view = await open({ grants, path })
expect(view.find('[data-testid="page"]').exists()).toBe(true)
expect(view.text()).not.toContain(NO_ACCESS_TITLE)
})
})
describe('The guard', () => {
it('shows neither the page nor the 403 while the session is checking', async () => {
const view = await open({ grants: 'media.view', path: '/schema', status: 'checking' })
expect(view.find('[data-testid="page"]').exists()).toBe(false)
expect(view.text()).not.toContain(NO_ACCESS_TITLE)
useSessionStore().status = 'ready'
await flushPromises()
expect(view.text()).toContain(NO_ACCESS_TITLE)
})
it('leaves an unverified session to the pending redirect', async () => {
const view = await open({ grants: '', path: '/schema', verified: false })
expect(view.text()).not.toContain(NO_ACCESS_TITLE)
})
it('switches to the 403 state when grants are lowered while the page is open', async () => {
const view = await open({ grants: 'schema.view', path: '/schema' })
expect(view.find('[data-testid="page"]').exists()).toBe(true)
const session = useSessionStore()
session.session = { ...session.session!, access: noAccess('Contributor') }
await flushPromises()
expect(view.find('[data-testid="page"]').exists()).toBe(false)
expect(view.text()).toContain(NO_ACCESS_TITLE)
expect(router.currentRoute.value.fullPath).toBe('/schema')
})
it('shows the gate copy in French', async () => {
setLocale('fr')
const view = await open({ grants: 'content.read=all', path: '/users' })
expect(view.text()).toContain('Vous n’avez pas accès à cette page')
})
})