// Point the DB at a closed port BEFORE requiring anything that builds the pool, // so any stray query fails fast instead of hanging the runner. These tests stub // every model method the controller touches, 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/admin/usersShard.controller') const users = require('../src/model/users/users.model') const shardLinks = require('../src/model/shardLinks/shardLinks.model') const shardState = require('../src/model/shardState/shardState.model') const shardEvents = require('../src/model/shardEvents/shardEvents.model') const { salesForAccounts } = require('../src/utils/shardSales') 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 }, } } // Save/restore the originals so each test's monkeypatches don't leak. const originals = { getById: users.getById, listForUser: shardLinks.listForUser, listHousesForAccounts: shardState.listHousesForAccounts, listOnlineForAccounts: shardState.listOnlineForAccounts, eventsList: shardEvents.list, } afterEach(() => { users.getById = originals.getById shardLinks.listForUser = originals.listForUser shardState.listHousesForAccounts = originals.listHousesForAccounts shardState.listOnlineForAccounts = originals.listOnlineForAccounts shardEvents.list = originals.eventsList }) // ── salesForAccounts util ────────────────────────────────────────────────── test('salesForAccounts returns [] for an empty account set without hitting the log', async () => { let called = false shardEvents.list = async () => { called = true return [] } assert.deepEqual(await salesForAccounts([]), []) assert.equal(called, false) }) test('salesForAccounts keeps only sales owned by the given accounts, newest 50', async () => { const events = [] // 60 sales owned by "mine", plus some owned by "other". for (let i = 0; i < 60; i++) { events.push({ t: i, payload: { ownerAcct: 'mine', itemType: 'sword', amount: 1, price: 10, commission: 1 } }) } events.push({ t: 999, payload: { ownerAcct: 'other', itemType: 'shield', amount: 1, price: 5 } }) shardEvents.list = async () => events const rows = await salesForAccounts(['mine']) assert.equal(rows.length, 50) // capped assert.ok(rows.every((r) => r.ownerAcct === 'mine')) // never leaks "other" assert.deepEqual(Object.keys(rows[0]).sort(), ['amount', 'commission', 'itemType', 'ownerAcct', 'price', 't']) }) // ── Controller: unknown user → 404 ───────────────────────────────────────── for (const handler of ['getUser', 'listAccounts', 'getSales', 'getHouses', 'getOnline']) { test(`${handler} returns 404 when the user does not exist`, async () => { users.getById = async () => null const res = mockRes() await ctrl[handler]({ params: { id: '404' } }, res) assert.equal(res.statusCode, 404) }) } // ── Controller: scoping to the user's accounts ───────────────────────────── test('listAccounts returns the user’s linked accounts', async () => { users.getById = async () => ({ id: 7, username: 'bob', role: 'player' }) shardLinks.listForUser = async (id) => { assert.equal(id, 7) return [{ account: 'acctA' }, { account: 'acctB' }] } const res = mockRes() await ctrl.listAccounts({ params: { id: '7' } }, res) assert.equal(res.statusCode, 200) assert.deepEqual(res.body, [{ account: 'acctA' }, { account: 'acctB' }]) }) test('getHouses passes exactly the user’s accounts to the model', async () => { users.getById = async () => ({ id: 7 }) shardLinks.listForUser = async () => [{ account: 'acctA' }, { account: 'acctB' }] let received = null shardState.listHousesForAccounts = async (accounts) => { received = accounts return [{ serial: '0x1', isIdoc: true }] } const res = mockRes() await ctrl.getHouses({ params: { id: '7' } }, res) assert.deepEqual(received, ['acctA', 'acctB']) assert.deepEqual(res.body, [{ serial: '0x1', isIdoc: true }]) }) test('getOnline passes exactly the user’s accounts to the model', async () => { users.getById = async () => ({ id: 7 }) shardLinks.listForUser = async () => [{ account: 'acctA' }] let received = null shardState.listOnlineForAccounts = async (accounts) => { received = accounts return [{ serial: '0x2', name: 'Zoe' }] } const res = mockRes() await ctrl.getOnline({ params: { id: '7' } }, res) assert.deepEqual(received, ['acctA']) assert.deepEqual(res.body, [{ serial: '0x2', name: 'Zoe' }]) }) test('a user with no linked accounts yields empty sales/houses/online', async () => { users.getById = async () => ({ id: 7 }) shardLinks.listForUser = async () => [] shardState.listHousesForAccounts = async (a) => (a.length ? [{}] : []) shardState.listOnlineForAccounts = async (a) => (a.length ? [{}] : []) shardEvents.list = async () => [{ payload: { ownerAcct: 'someoneElse' } }] const sales = mockRes() const houses = mockRes() const online = mockRes() await ctrl.getSales({ params: { id: '7' } }, sales) await ctrl.getHouses({ params: { id: '7' } }, houses) await ctrl.getOnline({ params: { id: '7' } }, online) assert.deepEqual(sales.body, []) assert.deepEqual(houses.body, []) assert.deepEqual(online.body, []) }) test('getUser returns the sanitized user row', async () => { users.getById = async () => ({ id: 7, username: 'bob', role: 'player', status: 'active' }) const res = mockRes() await ctrl.getUser({ params: { id: '7' } }, res) assert.equal(res.statusCode, 200) assert.equal(res.body.username, 'bob') })