// Staff-location visibility on GET /public/shard/online. The endpoint is // token-free, so it inspects the caller's session (getUserFromRequest) and only // includes each staff member's in-game location (map/x/y/z) for admins and // moderators. Players and the public still see who is online, but not where. // // Point the DB at a closed port BEFORE requiring anything that builds the pool, // so any stray query fails fast instead of hanging. The model + auth are stubbed, // so the DB is never actually hit. 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') const ctrl = require('../src/router/v1/public/shard.controller') const shardState = require('../src/model/shardState/shardState.model') const auth = require('../src/utils/auth') 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 }, } } // One online staff member with a location the model would return. const ONLINE_ROW = { serial: '0x1', name: 'Lady Mod', map: 'Felucca', x: 1495, y: 1628, z: 10 } const originals = { listOnlineLinked: shardState.listOnlineLinked, getUserFromRequest: auth.getUserFromRequest, } afterEach(() => { shardState.listOnlineLinked = originals.listOnlineLinked auth.getUserFromRequest = originals.getUserFromRequest }) // Stub the model to return the staff member, and the session to the given viewer. function setup(viewer) { shardState.listOnlineLinked = async () => [ONLINE_ROW] auth.getUserFromRequest = () => viewer } const LOCATION_KEYS = ['map', 'x', 'y', 'z'] for (const role of ['admin', 'moderator']) { test(`getOnline includes location for a ${role}`, async () => { setup({ id: 1, username: 'staff', role }) const res = mockRes() await ctrl.getOnline({}, res) assert.equal(res.statusCode, 200) assert.equal(res.body.length, 1) const entry = res.body[0] assert.equal(entry.name, 'Lady Mod') assert.equal(entry.serial, '0x1') assert.equal(entry.map, 'Felucca') assert.equal(entry.x, 1495) assert.equal(entry.y, 1628) assert.equal(entry.z, 10) }) } test('getOnline omits location for a logged-in player', async () => { setup({ id: 2, username: 'joe', role: 'player' }) const res = mockRes() await ctrl.getOnline({}, res) assert.equal(res.statusCode, 200) const entry = res.body[0] // Still shows they are online… assert.equal(entry.name, 'Lady Mod') assert.equal(entry.serial, '0x1') // …but the location fields are absent entirely (not null/placeholder). for (const k of LOCATION_KEYS) assert.ok(!(k in entry), `expected "${k}" to be omitted`) }) test('getOnline omits location for an unauthenticated request', async () => { setup(null) // getUserFromRequest returns null for anon callers const res = mockRes() await ctrl.getOnline({}, res) assert.equal(res.statusCode, 200) const entry = res.body[0] assert.equal(entry.name, 'Lady Mod') assert.equal(entry.serial, '0x1') for (const k of LOCATION_KEYS) assert.ok(!(k in entry), `expected "${k}" to be omitted`) }) // An editor is staff but not admin/moderator — they should not see location. test('getOnline omits location for an editor', async () => { setup({ id: 3, username: 'ed', role: 'editor' }) const res = mockRes() await ctrl.getOnline({}, res) const entry = res.body[0] for (const k of LOCATION_KEYS) assert.ok(!(k in entry), `expected "${k}" to be omitted`) })