// ── The chat-title conditions (PLAN_REDESIGNS §5, D172–D175, D209) ───────── // // Four things here, each a mistake that would look right in review: // // • **a best is not a sum.** The two distance columns take GREATEST at ingest; // adding two intervals' maxima would give a kill nobody made. // • **"NPC kills" subtracts in SIGNED arithmetic.** `npc_kills` is unsigned, // and a row counted before `animal_kills` existed must not wrap to 4 billion. // • **a rename reaches a rule with no text.** The rule's own text wins, then // the admin's category title, then the default (§4.6's three layers). // • **a category's title is never a rule's text by accident.** An empty text is // "use the category's", and a typed title made of markup is still refused. const test = require('node:test') const assert = require('node:assert') const { fakeCtx } = require('./_fakes') /** Installs a core whose `db.query` records every statement and answers `rows`. */ function withRecorder(rows = []) { const statements = [] const ctx = fakeCtx({ db: { query: (sql, params = []) => { statements.push({ sql, params }) return Promise.resolve(typeof rows === 'function' ? rows(sql, params) : rows) }, pool: {}, }, }) require('../core')._reset() require('../core').init(ctx) return { statements, touching: (table) => statements.filter((s) => s.sql.includes(table)), ctx, } } withRecorder() const titles = require('../model/titles/titles') const lists = require('../model/titles/lists') const frame = (over = {}) => ({ type: 'event', t: 1, serverId: 'main', wipeId: 'w1', ...over }) const item = (kind, over = {}) => ({ id: 1, t: 1, kind, frame: frame({ kind, ...over }) }) // ── The categories ──────────────────────────────────────────────────────── test('twenty-three categories carry a default title, and playtime carries none (D172)', () => { assert.strictEqual(titles.CATEGORY_IDS.length, 23) assert.ok(!titles.CATEGORY_IDS.includes('playtime')) assert.strictEqual(titles.STATS.playtime.title, null) assert.strictEqual(titles.STATS.animalkills.title, 'Beastbane') assert.strictEqual(titles.STATS.missions.title, 'Wayfarer') // Every id fits the column a rule stores it in. for (const id of Object.keys(titles.STATS)) assert.ok(id.length <= 16, id) // Every list a category names exists, and is not empty. for (const { source } of Object.values(titles.STATS)) { const name = source.weapons || source.gathered if (name) assert.ok(Array.isArray(lists[name]) && lists[name].length, name) } }) test('"NPC kills" is human NPCs: every NPC kill less the animals (D209)', () => { assert.deepStrictEqual(titles.STATS.npckills.source, { diff: ['npc_kills', 'animal_kills'] }) }) test('every blade is also a melee weapon (§5.3)', () => { for (const blade of lists.blade) assert.ok(lists.melee.includes(blade), blade) }) // ── Rules and their text ────────────────────────────────────────────────── test('a rule may leave its text empty when its category has a title, and not for playtime (D175)', () => { const ok = titles.validateSettings({ rules: [{ stat: 'animalkills', topN: 1, text: '', color: '#00ff00' }] }) assert.strictEqual(ok.ok, true) assert.strictEqual(ok.value.rules[0].text, '') const playtime = titles.validateSettings({ rules: [{ stat: 'playtime', topN: 1, text: '', color: '#00ff00' }] }) assert.strictEqual(playtime.ok, false) assert.ok(playtime.errors.some((e) => /rule 1: the title needs some text/.test(e))) }) test('a typed title made only of markup is refused, even where empty would be allowed (reading 3)', () => { const bad = titles.validateSettings({ rules: [{ stat: 'kills', topN: 1, text: '[]<>', color: '#ff0000' }] }) assert.strictEqual(bad.ok, false) assert.ok(bad.errors.some((e) => /the title needs some text/.test(e))) }) test('a rule’s text wins, then the admin’s category title, then the default (§4.6)', () => { const rules = [ { stat: 'kills', topN: 1, text: 'Top Gun', color: '#ff0000' }, { stat: 'animalkills', topN: 1, text: '', color: '#00ff00' }, { stat: 'headshots', topN: 1, text: '', color: '#0000ff' }, ] const resolved = titles.resolveRules(rules, { animalkills: 'Hunter', kills: 'Ignored' }) assert.deepStrictEqual(resolved.map((r) => r.text), ['Top Gun', 'Hunter', 'Crownbreaker']) }) test('a playtime rule with no text shows nothing rather than an empty chip', () => { const resolved = titles.resolveRules([{ stat: 'playtime', topN: 1, text: '', color: '#ffffff' }], {}) assert.deepStrictEqual(resolved, []) }) test('a category title is checked like a rule’s, and empty is the Reset', () => { assert.deepStrictEqual(titles.validateCategory('animalkills', ' [Hunter] '), { ok: true, text: 'Hunter' }) assert.deepStrictEqual(titles.validateCategory('animalkills', ''), { ok: true, text: '' }) assert.strictEqual(titles.validateCategory('animalkills', '<>').ok, false) assert.strictEqual(titles.validateCategory('animalkills', 'x'.repeat(25)).ok, false) assert.strictEqual(titles.validateCategory('playtime', 'Regular').ok, false, 'playtime has no category title') assert.strictEqual(titles.validateCategory('deaths', 'X').ok, false) }) // ── The ingest ──────────────────────────────────────────────────────────── test('a tally’s title counts are added, and its two distances kept as a best', async () => { const rec = withRecorder() const { apply } = require('../ingest') await apply( 'main', item('player.tally', { steamId: 'p1', animalKills: 2, headshots: 1, bestPvpM: 187.46, bestPveM: 42.1, crafted: { attire: 3, weapon: 1, tool: 9 }, built: 12, missions: 1, weaponKills: { 'bow_hunting.entity': 2, 'machete.weapon': 1 }, }), ) const titled = rec.touching('rust_player_wipe_stats').find((s) => s.sql.includes('animal_kills')) assert.ok(titled, 'one statement for the title counts') assert.match(titled.sql, /animal_kills = animal_kills \+ VALUES\(animal_kills\)/) assert.match(titled.sql, /best_pvp_m = GREATEST\(best_pvp_m, VALUES\(best_pvp_m\)\)/) assert.match(titled.sql, /best_pve_m = GREATEST\(best_pve_m, VALUES\(best_pve_m\)\)/) assert.doesNotMatch(titled.sql, /best_pvp_m = best_pvp_m \+/) const cols = titled.sql.match(/\(server_id, wipe_id, steam_id, ([^)]+), last_seen\)/)[1].split(', ') const value = (col) => titled.params[3 + cols.indexOf(col)] assert.strictEqual(value('animal_kills'), 2) assert.strictEqual(value('crafted_attire'), 3) assert.strictEqual(value('crafted_weapons'), 1) assert.strictEqual(value('built'), 12) assert.strictEqual(value('missions'), 1) assert.strictEqual(value('best_pvp_m'), 187.5) assert.strictEqual(value('rockets'), 0) const weapons = rec.touching('rust_weapon_kills') assert.deepStrictEqual( weapons.map((w) => [w.params[3], w.params[4]]), [['bow_hunting.entity', 2], ['machete.weapon', 1]], ) assert.match(weapons[0].sql, /kills = kills \+ VALUES\(kills\)/) }) test('an older plugin’s tally writes no title statement at all', async () => { const rec = withRecorder() const { apply } = require('../ingest') await apply('main', item('player.tally', { steamId: 'p1', npcKills: 1 })) assert.strictEqual(rec.touching('rust_player_wipe_stats').filter((s) => s.sql.includes('animal_kills')).length, 0) assert.strictEqual(rec.touching('rust_weapon_kills').length, 0) }) // ── The standings ───────────────────────────────────────────────────────── test('a category’s standings read from the right place, in the leaderboard’s order', async () => { const rec = withRecorder([]) const db = require('../model/titles/titles.db') const at = { serverId: 'main', wipeId: 'w1', lists, limit: 3 } await db.standings({ ...at, source: titles.STATS.npckills.source }) const human = rec.statements.at(-1) assert.match(human.sql, /CAST\(s\.npc_kills AS SIGNED\) - CAST\(s\.animal_kills AS SIGNED\)/) assert.match(human.sql, /ORDER BY value DESC, MAX\(s\.last_seen\) DESC/) await db.standings({ ...at, source: titles.STATS.pvpdistance.source }) assert.match(rec.statements.at(-1).sql, /MAX\(s\.best_pvp_m\) AS value/) await db.standings({ ...at, source: titles.STATS.bowkills.source }) const bow = rec.statements.at(-1) assert.match(bow.sql, /FROM rust_weapon_kills t/) assert.deepStrictEqual(bow.params.slice(2, 2 + lists.bow.length), lists.bow) assert.strictEqual(bow.params.at(-1), 3) await db.standings({ ...at, source: titles.STATS.ore.source }) assert.match(rec.statements.at(-1).sql, /FROM rust_gather_totals t/) const before = rec.statements.length assert.deepStrictEqual(await db.standings({ ...at, source: { weapons: 'nothing' } }), []) assert.strictEqual(rec.statements.length, before, 'an empty list asks nothing') }) test('a rename reaches every server’s next answer: the memo is forgotten fleet-wide', async () => { let override = null withRecorder((sql) => { if (sql.includes('FROM rust_title_categories')) return override ? [{ stat: 'animalkills', text: override }] : [] if (sql.includes('FROM rust_title_rules')) return [{ stat: 'animalkills', topN: 1, text: '', color: '#00ff00' }] if (sql.includes('title_mode')) return [{ mode: 'first', max: 2 }] if (sql.includes('SUM(s.animal_kills)')) return [{ steamId: '7', value: '4' }] return [] }) const model = require('../model/titles/titles.model') model.forget() const first = await model.heldFor('main', { wipeId: 'w1', now: 1000 }) assert.deepStrictEqual(first.get('7'), [{ text: 'Beastbane', color: '#00ff00' }]) override = 'Hunter' await model.setCategory('animalkills', 'Hunter', 1) const after = await model.heldFor('main', { wipeId: 'w1', now: 1001 }) assert.deepStrictEqual(after.get('7'), [{ text: 'Hunter', color: '#00ff00' }]) })