// Point the DB at a closed port BEFORE requiring the controller (its models build // the pool). Every model call is monkeypatched, so no query runs; db.close() at // the end releases the pool so the process exits cleanly. process.env.DB_HOST = '127.0.0.1' process.env.DB_PORT = '59999' const { test, after, afterEach } = require('node:test') const assert = require('node:assert/strict') // Unit-test the public CMS/wiki controller's decision logic: // - getPage: staff see drafts (live preview); the public gets a 404 for a draft, // indistinguishable from a missing page (draft visibility is a boundary); // - getPagePreview: only a valid, matching preview token unlocks a draft; // - getWikiList: full-text search takes precedence, and an unknown category/tag // yields [] rather than an error; // - getPost(s): an unknown category is a 404; // - contact: a mailer failure surfaces as a 502, not a 500 or a throw. const ctrl = require('../src/router/v1/public/public.controller') const posts = require('../src/model/posts/posts.model') const wiki = require('../src/model/wiki/wiki.model') const pages = require('../src/model/pages/pages.model') const mailer = require('../src/utils/mailer') const token = require('../src/auth/token') const sessionService = require('../src/auth/session.service') const db = require('../src/utils/db') after(() => db.close()) function mockRes() { return { statusCode: 200, body: null, status(c) { this.statusCode = c return this }, json(b) { this.body = b return this }, } } // getUserFromRequest (destructured into the controller) delegates to // sessionService.validateSession — drive staff/public from there. function asStaff(role = 'admin') { sessionService.validateSession = () => ({ userId: 1, username: 'boss', role }) } function asPublic() { sessionService.validateSession = () => null } const originals = { validateSession: sessionService.validateSession, getBySlug: pages.getBySlug, getById: pages.getById, isValidUrlCategory: posts.isValidUrlCategory, listPublished: posts.listPublished, getPublished: posts.getPublished, wikiSearch: wiki.search, getCategoryBySlug: wiki.getCategoryBySlug, getTagBySlug: wiki.getTagBySlug, wikiListPublished: wiki.listPublished, sendContactMessage: mailer.sendContactMessage, } afterEach(() => { sessionService.validateSession = originals.validateSession pages.getBySlug = originals.getBySlug pages.getById = originals.getById posts.isValidUrlCategory = originals.isValidUrlCategory posts.listPublished = originals.listPublished posts.getPublished = originals.getPublished wiki.search = originals.wikiSearch wiki.getCategoryBySlug = originals.getCategoryBySlug wiki.getTagBySlug = originals.getTagBySlug wiki.listPublished = originals.wikiListPublished mailer.sendContactMessage = originals.sendContactMessage }) // ── getPage draft visibility ──────────────────────────────────────────── test('getPage lets staff include unpublished drafts', async () => { asStaff('editor') let sawOpts pages.getBySlug = async (slug, opts) => { sawOpts = opts return { slug, status: 'draft' } } const res = mockRes() await ctrl.getPage({ params: { slug: 'wip' } }, res) assert.equal(sawOpts.includeUnpublished, true) assert.equal(res.body.slug, 'wip') }) test('getPage hides drafts from the public and 404s (model returns null)', async () => { asPublic() let sawOpts pages.getBySlug = async (slug, opts) => { sawOpts = opts return null // model already filtered the draft out for a public caller } const res = mockRes() await ctrl.getPage({ params: { slug: 'wip' } }, res) assert.equal(sawOpts.includeUnpublished, false) assert.equal(res.statusCode, 404) }) test('getPage treats a player role as non-staff (no draft access)', async () => { asStaff('player') // a player is NOT in STAFF_ROLES let sawOpts pages.getBySlug = async (slug, opts) => { sawOpts = opts return null } await ctrl.getPage({ params: { slug: 'wip' } }, mockRes()) assert.equal(sawOpts.includeUnpublished, false) }) // ── getPagePreview token gate ─────────────────────────────────────────── test('getPagePreview unlocks a draft with a valid, matching preview token', async () => { asPublic() const validToken = token.signPagePreview(42) pages.getById = async (id) => ({ id, status: 'draft' }) const res = mockRes() await ctrl.getPagePreview({ params: { id: '42', token: validToken } }, res) assert.equal(res.statusCode, 200) assert.equal(res.body.id, 42) }) test('getPagePreview 404s when the token is for a different page', async () => { const tokenForOther = token.signPagePreview(7) let loaded = false pages.getById = async () => { loaded = true return { id: 42 } } const res = mockRes() await ctrl.getPagePreview({ params: { id: '42', token: tokenForOther } }, res) assert.equal(res.statusCode, 404) assert.equal(loaded, false, 'a mismatched token never loads the page') }) test('getPagePreview 404s on a garbage token', async () => { const res = mockRes() await ctrl.getPagePreview({ params: { id: '42', token: 'not-a-jwt' } }, res) assert.equal(res.statusCode, 404) }) // ── getWikiList precedence + unknown filters ──────────────────────────── test('getWikiList runs a full-text search when q is present, ignoring filters', async () => { let searched wiki.search = async (q, opts) => { searched = { q, opts } return [{ slug: 'hit' }] } wiki.listPublished = async () => { throw new Error('listPublished should not run when q is set') } const res = mockRes() await ctrl.getWikiList({ query: { q: ' dragon ', category: 'bestiary' } }, res) assert.equal(searched.q, 'dragon') // trimmed assert.equal(searched.opts.publishedOnly, true) assert.equal(res.body[0].slug, 'hit') }) test('getWikiList returns [] for an unknown category filter without listing pages', async () => { wiki.getCategoryBySlug = async () => null let listed = false wiki.listPublished = async () => { listed = true return [] } const res = mockRes() await ctrl.getWikiList({ query: { category: 'ghosts' } }, res) assert.deepEqual(res.body, []) assert.equal(listed, false) }) test('getWikiList combines a known category and tag into the list filter', async () => { wiki.getCategoryBySlug = async () => ({ id: 3 }) wiki.getTagBySlug = async () => ({ id: 9 }) let filters wiki.listPublished = async (f) => { filters = f return [] } await ctrl.getWikiList({ query: { category: 'lore', tag: 'undead' } }, mockRes()) assert.deepEqual(filters, { categoryId: 3, tagId: 9 }) }) // ── posts category validation ─────────────────────────────────────────── test('getPosts 404s an unknown url category', async () => { posts.isValidUrlCategory = () => false const res = mockRes() await ctrl.getPosts({ params: { category: 'nope' } }, res) assert.equal(res.statusCode, 404) }) test('getPost 404s a valid category with no matching post', async () => { posts.isValidUrlCategory = () => true posts.getPublished = async () => null const res = mockRes() await ctrl.getPost({ params: { category: 'news', idOrSlug: 'missing' } }, res) assert.equal(res.statusCode, 404) }) // ── contact failure path ──────────────────────────────────────────────── test('contact surfaces a mailer failure as a 502 (not a 500 or a throw)', async () => { mailer.sendContactMessage = async () => { throw new Error('smtp down') } const res = mockRes() await ctrl.contact({ body: { name: 'A', email: 'a@b.c', message: 'hi' } }, res) assert.equal(res.statusCode, 502) assert.match(res.body.message, /send/i) })