Files
admin/apps/backend/tests/functional/web_manager/pages_api.spec.ts
T

312 lines
12 KiB
TypeScript

import { test } from '@japa/runner'
import testUtils from '@adonisjs/core/services/test_utils'
import WebManagerService from '#base_modules/WebManager/services/web_manager_service'
import { createAdminWithToken } from '#tests/utils/auth'
import { contentComponent, pageInput } from '#tests/utils/web_manager'
const ALL_PERMISSIONS = [
'web_manager.view',
'web_manager.create',
'web_manager.update',
'web_manager.publish',
'web_manager.delete',
'web_manager.settings',
]
/** [method, path, permission the action authorizes against] */
const ADMIN_ROUTES = [
['get', '/web-manager/pages', 'web_manager.view'],
['get', '/web-manager/pages/group-keys', 'web_manager.view'],
['post', '/web-manager/pages', 'web_manager.create'],
['get', '/web-manager/pages/1', 'web_manager.view'],
['put', '/web-manager/pages/1/draft', 'web_manager.update'],
['post', '/web-manager/pages/1/publish', 'web_manager.publish'],
['post', '/web-manager/pages/1/unpublish', 'web_manager.publish'],
['post', '/web-manager/pages/1/preview-token', 'web_manager.view'],
['delete', '/web-manager/pages/1', 'web_manager.delete'],
['get', '/web-manager/settings', 'web_manager.view'],
['put', '/web-manager/settings', 'web_manager.settings'],
] as const
test.group('WebManager admin API auth', (group) => {
group.each.setup(() => testUtils.db().wrapInGlobalTransaction())
test('every admin route rejects an unauthenticated request', async ({ client }) => {
for (const [method, url] of ADMIN_ROUTES) {
const response = await client[method](url)
response.assertStatus(401)
}
})
test('every action rejects a token that lacks its permission', async ({ client }) => {
// holds every permission except the one each action checks
for (const [method, url, permission] of ADMIN_ROUTES) {
const { token } = await createAdminWithToken(
ALL_PERMISSIONS.filter((slug) => slug !== permission)
)
const response = await client[method](url).bearerToken(token)
response.assertStatus(403)
}
})
test('a viewer can read but not mutate', async ({ client }) => {
const { token } = await createAdminWithToken(['web_manager.view'])
const list = await client.get('/web-manager/pages').bearerToken(token)
list.assertStatus(200)
const create = await client.post('/web-manager/pages').bearerToken(token).json(pageInput())
create.assertStatus(403)
})
})
test.group('WebManager admin API', (group) => {
group.each.setup(() => testUtils.db().wrapInGlobalTransaction())
test('create → publish → unpublish round trip reports revalidation', async ({
assert,
client,
}) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const created = await client
.post('/web-manager/pages')
.bearerToken(token)
.json(pageInput({ components: [contentComponent()] }))
created.assertStatus(201)
const pageId = created.body().data.page.id
assert.equal(created.body().data.page.status, 'draft')
const published = await client.post(`/web-manager/pages/${pageId}/publish`).bearerToken(token)
published.assertStatus(200)
assert.equal(published.body().data.page.status, 'published')
assert.equal(published.body().meta.revalidation.status, 'disabled')
assert.isNotEmpty(published.body().meta.revalidation.tags)
const unpublished = await client
.post(`/web-manager/pages/${pageId}/unpublish`)
.bearerToken(token)
unpublished.assertStatus(200)
assert.equal(unpublished.body().data.page.status, 'unpublished')
const again = await client.post(`/web-manager/pages/${pageId}/unpublish`).bearerToken(token)
again.assertStatus(409)
assert.equal(again.body().code, 'E_PAGE_NOT_PUBLISHED')
})
test('page list filters by status and search', async ({ assert, client }) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const service = new WebManagerService()
const { page: draft } = await service.createDraft(pageInput({ title: 'Needle draft' }))
const { page: live } = await service.createDraft(pageInput({ title: 'Other page' }))
await service.publish(live.id)
const drafts = await client
.get('/web-manager/pages')
.qs({ status: 'draft', search: 'needle' })
.bearerToken(token)
drafts.assertStatus(200)
assert.deepEqual(
drafts.body().data.map((row: { id: number }) => row.id),
[draft.id]
)
})
test('validation rejects a malformed path with 422', async ({ client }) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const response = await client
.post('/web-manager/pages')
.bearerToken(token)
.json(pageInput({ path: 'About/Us' }))
response.assertStatus(422)
})
test('an unknown block type is rejected with 422', async ({ assert, client }) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const response = await client
.post('/web-manager/pages')
.bearerToken(token)
.json(pageInput({ components: [{ type: 'carousel', data: {} }] }))
response.assertStatus(422)
assert.equal(response.body().code, 'E_INVALID_PAGE_PAYLOAD')
})
test('a multipart payload field is parsed as JSON', async ({ assert, client }) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const response = await client
.post('/web-manager/pages')
.bearerToken(token)
.field('payload', JSON.stringify(pageInput({ components: [contentComponent()] })))
response.assertStatus(201)
assert.equal(response.body().data.components.length, 1)
})
test('empty strings survive the multipart payload but not a JSON body', async ({
assert,
client,
}) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const input = pageInput({
components: [
{
type: 'content',
data: { title: 'Section', subtitle: '', columns: [] },
},
],
})
// The JSON bodyparser converts '' to null, so the strict block validator
// rejects it — this is why the admin sends the page as one JSON string.
const asJson = await client.post('/web-manager/pages').bearerToken(token).json(input)
asJson.assertStatus(422)
assert.equal(asJson.body().code, 'E_INVALID_PAGE_PAYLOAD')
const asPayload = await client
.post('/web-manager/pages')
.bearerToken(token)
.field('payload', JSON.stringify(input))
asPayload.assertStatus(201)
})
test('a malformed multipart payload is rejected with 422', async ({ assert, client }) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const response = await client
.post('/web-manager/pages')
.bearerToken(token)
.field('payload', 'not-json')
response.assertStatus(422)
assert.equal(response.body().code, 'E_INVALID_PAGE_PAYLOAD')
})
test('a media value referencing a missing upload is rejected with 422', async ({
assert,
client,
}) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const input = pageInput({
hero: { type: 'middle', ctaItems: [], media: { name: 'x.png', fileKey: 'file-0' } },
})
const response = await client
.post('/web-manager/pages')
.bearerToken(token)
.field('payload', JSON.stringify(input))
response.assertStatus(422)
assert.equal(response.body().code, 'E_PAGE_MEDIA_FILE_MISSING')
})
test('settings upsert requires its own permission and reports revalidation', async ({
assert,
client,
}) => {
const settings = {
locale: 'en',
header: { menus: [], ctaItems: [] },
footer: { copyright: '© Spec', menus: [] },
legal: { privacyPath: '/privacy', termsPath: '/terms' },
}
const { token: viewer } = await createAdminWithToken(['web_manager.view'])
const denied = await client.put('/web-manager/settings').bearerToken(viewer).json(settings)
denied.assertStatus(403)
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const saved = await client.put('/web-manager/settings').bearerToken(token).json(settings)
saved.assertStatus(200)
assert.equal(saved.body().meta.revalidation.status, 'disabled')
assert.deepEqual(saved.body().meta.revalidation.tags, ['web-manager:settings:en'])
})
})
test.group('WebManager public API', (group) => {
group.each.setup(() => testUtils.db().wrapInGlobalTransaction())
test('resolve serves published pages only', async ({ assert, client }) => {
const service = new WebManagerService()
const { page } = await service.createDraft(pageInput({ title: 'Public page' }))
const asDraft = await client
.get('/web-manager/public/pages/resolve')
.qs({ locale: 'en', path: page.path })
asDraft.assertStatus(404)
await service.publish(page.id)
const asPublished = await client
.get('/web-manager/public/pages/resolve')
.qs({ locale: 'en', path: page.path })
asPublished.assertStatus(200)
assert.equal(asPublished.body().page.title, 'Public page')
assert.equal(asPublished.body().page.status, 'published')
assert.isArray(asPublished.body().breadcrumbs)
assert.isArray(asPublished.body().languageAlternatives)
})
test('preview serves a draft through a signed token and rejects tampering', async ({
assert,
client,
}) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const service = new WebManagerService()
const { page } = await service.createDraft(pageInput({ title: 'Draft preview' }))
const minted = await client
.post(`/web-manager/pages/${page.id}/preview-token`)
.bearerToken(token)
.json({})
minted.assertStatus(201)
const previewToken: string = minted.body().data.token
const preview = await client
.get('/web-manager/public/pages/preview')
.qs({ token: previewToken })
preview.assertStatus(200)
assert.equal(preview.body().page.title, 'Draft preview')
assert.equal(preview.body().page.status, 'draft')
const tampered = await client
.get('/web-manager/public/pages/preview')
.qs({ token: `${previewToken}x` })
tampered.assertStatus(404)
})
test('sitemap lists published root pages with pagination', async ({ assert, client }) => {
const service = new WebManagerService()
const { page } = await service.createDraft(pageInput())
await service.publish(page.id)
await service.createDraft(pageInput()) // stays draft, must not appear
const response = await client.get('/web-manager/public/pages/sitemap')
response.assertStatus(200)
const items = response.body().items as Array<{ id: number }>
assert.deepEqual(
items.map((item) => item.id),
[page.id]
)
assert.properties(response.body().pagination, ['page', 'perPage', 'total', 'lastPage'])
})
test('blog listing returns published blog items, newest first', async ({ assert, client }) => {
const service = new WebManagerService()
const { page: older } = await service.createDraft(
pageInput({ pageType: 'blog-item', title: 'Older post' })
)
await service.publish(older.id)
const { page: newer } = await service.createDraft(
pageInput({ pageType: 'blog-item', title: 'Newer post' })
)
await service.publish(newer.id)
const { page: landing } = await service.createDraft(pageInput({ title: 'Not a post' }))
await service.publish(landing.id)
const response = await client.get('/web-manager/public/blog/pages').qs({ locale: 'en' })
response.assertStatus(200)
const titles = (response.body().items as Array<{ title: string }>).map((item) => item.title)
assert.deepEqual(titles, ['Newer post', 'Older post'])
})
})