windowValue mapped only the 24h/7d keys and used `?? row.d30` as the fallback:
const col = { '24h': row.d1, '7d': row.d7 }[key] ?? row.d30
so a null d1/d7 (which the function is documented to tolerate) returned the
30-day count instead of 0, inflating the 24h/7d moderation tiles. It happens to
be masked today because `SUM(created_at >= ?)` nulls d1/d7/d30 only in unison,
but the contract is wrong and the existing test used an all-null row that hid it.
Map all three window keys explicitly so each reads its own column and a null
coerces to 0 via `Number(col) || 0`. Add a regression test with a null narrow
column and a non-null d30.
Co-Authored-By: Claude <noreply@anthropic.com>
99 lines
4.3 KiB
JavaScript
99 lines
4.3 KiB
JavaScript
// Unit tests for the moderation dashboard's pure reshaping/annotation logic.
|
|
// DB-free (like the rest of this suite) — the SQL layer is exercised manually
|
|
// against a dev database per the plan's verification steps.
|
|
const { test } = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
|
|
const moderation = require('../src/model/moderation/moderation.pure')
|
|
|
|
test('reshapeWindows: folds rows into windows and zero-fills missing types', () => {
|
|
const rows = [
|
|
{ action_type: 'ban', d1: 1, d7: 3, d30: 5 },
|
|
{ action_type: 'warn', d1: 0, d7: 2, d30: 9 },
|
|
]
|
|
const { windows } = moderation.reshapeWindows(rows)
|
|
assert.deepEqual(windows['24h'], { ban: 1, kick: 0, mute: 0, warn: 0 })
|
|
assert.deepEqual(windows['7d'], { ban: 3, kick: 0, mute: 0, warn: 2 })
|
|
assert.deepEqual(windows['30d'], { ban: 5, kick: 0, mute: 0, warn: 9 })
|
|
})
|
|
|
|
test('reshapeWindows: coerces string/decimal SUM results to numbers', () => {
|
|
const { windows } = moderation.reshapeWindows([{ action_type: 'mute', d1: '2', d7: '2', d30: '4' }])
|
|
assert.strictEqual(windows['24h'].mute, 2)
|
|
assert.strictEqual(windows['30d'].mute, 4)
|
|
})
|
|
|
|
test('reshapeWindows: ignores unknown action types (e.g. future enum values)', () => {
|
|
const { windows } = moderation.reshapeWindows([{ action_type: 'filter_hit', d1: 9, d7: 9, d30: 9 }])
|
|
assert.deepEqual(windows['24h'], { ban: 0, kick: 0, mute: 0, warn: 0 })
|
|
})
|
|
|
|
test('reshapeWindows: empty input yields all-zero windows', () => {
|
|
const { windows } = moderation.reshapeWindows([])
|
|
assert.deepEqual(windows, {
|
|
'24h': { ban: 0, kick: 0, mute: 0, warn: 0 },
|
|
'7d': { ban: 0, kick: 0, mute: 0, warn: 0 },
|
|
'30d': { ban: 0, kick: 0, mute: 0, warn: 0 },
|
|
})
|
|
})
|
|
|
|
test('annotate: flags automated when staff id matches the bot application id', () => {
|
|
const [row] = moderation.annotate([{ staff_user_id: '999', target_site_user_id: null }], '999')
|
|
assert.equal(row.is_automated, true)
|
|
})
|
|
|
|
test('annotate: string/number snowflake mismatch still matches (coerced)', () => {
|
|
// mod_actions stores staff_user_id as VARCHAR, but bot_config.application_id
|
|
// could arrive as a number — the compare must coerce both sides.
|
|
const [row] = moderation.annotate([{ staff_user_id: 999, target_site_user_id: null }], '999')
|
|
assert.equal(row.is_automated, true)
|
|
})
|
|
|
|
test('annotate: staff action (id differs from bot) is not automated', () => {
|
|
const [row] = moderation.annotate([{ staff_user_id: '111', target_site_user_id: null }], '999')
|
|
assert.equal(row.is_automated, false)
|
|
})
|
|
|
|
test('annotate: no bot application id configured means nothing is automated', () => {
|
|
const [row] = moderation.annotate([{ staff_user_id: '999', target_site_user_id: null }], null)
|
|
assert.equal(row.is_automated, false)
|
|
})
|
|
|
|
test('annotate: folds joined identity columns into linked_account', () => {
|
|
const [row] = moderation.annotate(
|
|
[{ staff_user_id: '1', target_site_user_id: 7, target_site_username: 'perry' }],
|
|
null,
|
|
)
|
|
assert.deepEqual(row.linked_account, { id: 7, username: 'perry' })
|
|
})
|
|
|
|
test('annotate: no linked identity yields null linked_account', () => {
|
|
const [row] = moderation.annotate([{ staff_user_id: '1', target_site_user_id: null }], null)
|
|
assert.equal(row.linked_account, null)
|
|
})
|
|
|
|
test('windowValue: picks the right column per window key and coerces to number', () => {
|
|
const row = { d1: '2', d7: 5, d30: '11' }
|
|
assert.strictEqual(moderation.windowValue(row, '24h'), 2)
|
|
assert.strictEqual(moderation.windowValue(row, '7d'), 5)
|
|
assert.strictEqual(moderation.windowValue(row, '30d'), 11)
|
|
})
|
|
|
|
test('windowValue: null row (no rows in window) yields 0', () => {
|
|
assert.strictEqual(moderation.windowValue(null, '24h'), 0)
|
|
assert.strictEqual(moderation.windowValue(undefined, '30d'), 0)
|
|
})
|
|
|
|
test('windowValue: null sum column yields 0', () => {
|
|
assert.strictEqual(moderation.windowValue({ d1: null, d7: null, d30: null }, '7d'), 0)
|
|
})
|
|
|
|
test('windowValue: a null narrow-window column never falls back to the 30d total', () => {
|
|
// Regression: an earlier rewrite used `{...}[key] ?? row.d30`, so a null d1/d7
|
|
// returned the 30-day count instead of 0 — inflating the 24h/7d tiles.
|
|
const row = { d1: null, d7: null, d30: 5 }
|
|
assert.strictEqual(moderation.windowValue(row, '24h'), 0)
|
|
assert.strictEqual(moderation.windowValue(row, '7d'), 0)
|
|
assert.strictEqual(moderation.windowValue(row, '30d'), 5)
|
|
})
|