Add initial project files
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
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'])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user