feat(news): auto-push published news to the in-game Town Cryer News gump (2.1)
Phase 4: sync the site's published news posts into the Protocol 2.1 News gump. - uoLinkClient.postNews / deleteNews. - utils/newsGump.js — a STATE SYNC (not a one-shot announce leg): an article stays in the gump while its post is published news and is pulled when it leaves that state. buildArticle renders a compact gump-HTML block (centred title + plain-text excerpt — the gump supports only a small HTML subset) with a "more info" link to /site/news and an optional gump image from the `news_gump_image` setting. Every call is best-effort / never-throws. - Hooked into the posts pipeline alongside the existing announce enqueue: syncPost on create/update/publish (fresh publish announces; edits refresh silently; leaving published-news pulls the article), removePost on delete. - reassertAll() runs in uoLinkSocket.backfill on every WS (re)connect — reconciles the gump to our source of truth and recovers any article whose original live push failed (silent, so a reconnect never re-proclaims old news). Server 185/185, swagger regenerated. Refs .plans/protocol2-integration.md (Phase 4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
69
server/test/newsGump.test.js
Normal file
69
server/test/newsGump.test.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const { test, beforeEach, afterEach } = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
|
||||
// Exercise the News-gump sync decisions against a fake sidecar client by
|
||||
// monkeypatching the shared modules newsGump require()s (same instance) — no DB,
|
||||
// no network.
|
||||
const uoLinkClient = require('../src/utils/uoLinkClient')
|
||||
const settings = require('../src/model/settings/settings.model')
|
||||
const newsGump = require('../src/utils/newsGump')
|
||||
|
||||
let calls
|
||||
const saved = {}
|
||||
|
||||
beforeEach(() => {
|
||||
calls = { post: [], del: [] }
|
||||
saved.postNews = uoLinkClient.postNews
|
||||
saved.deleteNews = uoLinkClient.deleteNews
|
||||
saved.get = settings.get
|
||||
uoLinkClient.postNews = async (article) => { calls.post.push(article); return { ok: true, status: 200 } }
|
||||
uoLinkClient.deleteNews = async (id) => { calls.del.push(id); return { ok: true, status: 200 } }
|
||||
settings.get = async () => null // no gump image configured
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
uoLinkClient.postNews = saved.postNews
|
||||
uoLinkClient.deleteNews = saved.deleteNews
|
||||
settings.get = saved.get
|
||||
})
|
||||
|
||||
const newsPost = (over = {}) => ({ id: 42, category: 'news', published: true, title: 'Double XP Weekend', excerpt: 'Starts Friday.', body: null, ...over })
|
||||
|
||||
test('buildArticle centres the title, links the news list, and respects announce', async () => {
|
||||
const a = await newsGump.buildArticle(newsPost(), { announce: false })
|
||||
assert.equal(a.id, '42')
|
||||
assert.match(a.body, /<CENTER>Double XP Weekend<\/CENTER>/)
|
||||
assert.match(a.body, /Starts Friday\./)
|
||||
assert.match(a.url, /\/site\/news$/)
|
||||
assert.equal(a.announce, false)
|
||||
})
|
||||
|
||||
test('a fresh publish into news pushes with announce=true', async () => {
|
||||
await newsGump.syncPost(newsPost(), { wasPublished: false, wasNews: false })
|
||||
assert.equal(calls.post.length, 1)
|
||||
assert.equal(calls.post[0].announce, true)
|
||||
assert.equal(calls.del.length, 0)
|
||||
})
|
||||
|
||||
test('an edit of already-published news refreshes silently (announce=false)', async () => {
|
||||
await newsGump.syncPost(newsPost({ title: 'Edited' }), { wasPublished: true, wasNews: true })
|
||||
assert.equal(calls.post.length, 1)
|
||||
assert.equal(calls.post[0].announce, false)
|
||||
})
|
||||
|
||||
test('unpublishing published news pulls the article from the gump', async () => {
|
||||
await newsGump.syncPost(newsPost({ published: false }), { wasPublished: true, wasNews: true })
|
||||
assert.equal(calls.post.length, 0)
|
||||
assert.deepEqual(calls.del, ['42'])
|
||||
})
|
||||
|
||||
test('a draft never-published news post does nothing', async () => {
|
||||
await newsGump.syncPost(newsPost({ published: false }), { wasPublished: false, wasNews: false })
|
||||
assert.equal(calls.post.length, 0)
|
||||
assert.equal(calls.del.length, 0)
|
||||
})
|
||||
|
||||
test('a non-news post (e.g. screenshot) is never pushed', async () => {
|
||||
await newsGump.syncPost(newsPost({ category: 'screenshot' }), { wasPublished: false, wasNews: false })
|
||||
assert.equal(calls.post.length, 0)
|
||||
})
|
||||
Reference in New Issue
Block a user