feat(public): type the brand block so mobile clients get typed theming
Branding is already returned by GET /public/settings (the `brand` block: name/colors/logo/hero/favicon, per-shard from BRAND_*). §8.6 of the Android plan asks to confirm it — this makes it a first-class part of the contract so the app's OpenAPI codegen produces typed branding instead of an untyped map. - Swagger: add Brand + PublicSettings schemas; /public/settings now references PublicSettings (was additionalProperties:true). Brand documents that asset fields may be site-relative paths (resolve against the base URL). - test/publicBrand.test.js locks the brand theming contract the app depends on (all fields present; BRAND_* defaults; admin site_title/contact_email overrides; accentInt never leaked). No behavior change to the response — it already carried `brand`; this types and guards it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
This commit is contained in:
57
server/test/publicBrand.test.js
Normal file
57
server/test/publicBrand.test.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// Point the DB at a closed port before the pool is built; getPublic() is fully
|
||||
// monkeypatched below so no query runs, and db.close() releases the pool at the
|
||||
// end so the process exits cleanly.
|
||||
process.env.DB_HOST = '127.0.0.1'
|
||||
process.env.DB_PORT = '59999'
|
||||
|
||||
const { test, beforeEach, afterEach, after } = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
|
||||
// Lock the /public/settings brand contract the mobile app themes itself from
|
||||
// (§8.6 of the Android plan). Exercises settings.getPublic() against an in-memory
|
||||
// fake by monkeypatching settings.db — no DB. The brand block is sourced from the
|
||||
// BRAND_* config defaults, with admin site_title / contact_email overriding.
|
||||
const settingsDb = require('../src/model/settings/settings.db')
|
||||
const settings = require('../src/model/settings/settings.model')
|
||||
const brand = require('../src/config/brand')
|
||||
const db = require('../src/utils/db')
|
||||
|
||||
after(() => db.close())
|
||||
|
||||
let savedGetAll
|
||||
beforeEach(() => {
|
||||
savedGetAll = settingsDb.getAll
|
||||
settingsDb.getAll = async () => [] // no stored settings → pure BRAND_* defaults
|
||||
})
|
||||
afterEach(() => {
|
||||
settingsDb.getAll = savedGetAll
|
||||
})
|
||||
|
||||
const THEMING_FIELDS = ['name', 'shortName', 'tagline', 'description', 'contactEmail', 'url', 'accent', 'logo', 'hero', 'favicon']
|
||||
|
||||
test('getPublic exposes the full brand theming contract the app depends on', async () => {
|
||||
const pub = await settings.getPublic()
|
||||
assert.ok(pub.brand, 'brand block present')
|
||||
for (const key of THEMING_FIELDS) {
|
||||
assert.ok(key in pub.brand, `brand.${key} present`)
|
||||
}
|
||||
// Defaults flow from BRAND_* config when nothing is stored.
|
||||
assert.equal(pub.brand.name, brand.name)
|
||||
assert.equal(pub.brand.accent, brand.accent)
|
||||
assert.equal(pub.brand.logo, brand.logo)
|
||||
assert.equal(pub.brand.hero, brand.hero)
|
||||
assert.equal(pub.brand.favicon, brand.favicon)
|
||||
// Never leak the Discord-only integer accent form to a public client.
|
||||
assert.equal(pub.brand.accentInt, undefined)
|
||||
})
|
||||
|
||||
test('admin site_title / contact_email override the brand defaults', async () => {
|
||||
settingsDb.getAll = async () => [
|
||||
{ key: 'site_title', value: 'My Shard' },
|
||||
{ key: 'contact_email', value: 'hi@shard.tld' },
|
||||
]
|
||||
const pub = await settings.getPublic()
|
||||
assert.equal(pub.brand.name, 'My Shard') // site_title overrides brand.name
|
||||
assert.equal(pub.brand.contactEmail, 'hi@shard.tld') // contact_email overrides
|
||||
assert.equal(pub.brand.accent, brand.accent) // colors still from config
|
||||
})
|
||||
Reference in New Issue
Block a user