test(server): port core's UO suite onto the ctx harness
22 test files moved from core, plus the two that were split out of files core keeps. 351 tests pass. One change runs through every moved test, and it is the boundary rather than a chore: core internals can no longer be stubbed by requiring them, because there are none to require. `../utils/db` and `../model/settings` do not exist here. What a test controls instead is the ctx core would have handed over, installed once by test/_setup.js -- which is a better seam anyway, since it is exactly the surface the contract promises and nothing wider. The ctx _setup installs is deliberately unfrozen. Core freezes what it hands a module and entry.test.js still asserts against a frozen one; but a test that needs settings.get to return a path has to be able to say so. Two tests changed SHAPE, and that is the boundary too. fromShardEvent used to assert through publish() into pushDevices and a captured fetch -- which endpoints were hit, how many requests went out. None of that is this module's any more: publish is ctx.push.publish, and the device registry and the relay are behind it. Reaching for them from here would be reaching past ctx. What remains is what the module owns and is the part worth guarding: a game account resolves to a website user, a personal target that resolves to nobody is dropped rather than published, and a sensitive kind never reaches publish at all. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
194
server/test/clilocSource.test.js
Normal file
194
server/test/clilocSource.test.js
Normal file
@@ -0,0 +1,194 @@
|
||||
const { test } = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
|
||||
const {
|
||||
ClilocSourceError,
|
||||
CUSTOM_DIR,
|
||||
resolveBase,
|
||||
listCustom,
|
||||
readSources,
|
||||
hashSources,
|
||||
sameSources,
|
||||
missingSources,
|
||||
readCliloc,
|
||||
} = require('../utils/clilocSource')
|
||||
|
||||
// The fs layer, exercised against real temp directories rather than mocks —
|
||||
// the behaviours that matter here (which file wins, what a directory listing
|
||||
// yields, what happens when one vanishes) are precisely the ones a mock would
|
||||
// define away.
|
||||
|
||||
function tmpdir() {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cliloc-'))
|
||||
return dir
|
||||
}
|
||||
|
||||
const tsv = (entries) => entries.map(([n, t]) => `${n}\t${t}`).join('\n') + '\n'
|
||||
|
||||
function write(dir, name, contents) {
|
||||
const file = path.join(dir, name)
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true })
|
||||
fs.writeFileSync(file, contents)
|
||||
return file
|
||||
}
|
||||
|
||||
// ── Resolution ─────────────────────────────────────────────────────────────
|
||||
|
||||
test('resolveBase: a directory picks the most specific candidate', () => {
|
||||
const dir = tmpdir()
|
||||
// A converted file sitting next to the client's own compressed one must win —
|
||||
// otherwise pointing at a client folder finds the file that will be rejected.
|
||||
write(dir, 'cliloc.enu', 'ignored')
|
||||
write(dir, 'clilocs.tsv', tsv([[1023721, 'quarter staff']]))
|
||||
assert.equal(path.basename(resolveBase(dir).base), 'clilocs.tsv')
|
||||
})
|
||||
|
||||
test('resolveBase: a file path roots overlays at its DIRECTORY', () => {
|
||||
// An operator who pointed at a file should not have to re-point at its folder
|
||||
// just to add a custom/ directory beside it.
|
||||
const dir = tmpdir()
|
||||
const file = write(dir, 'clilocs.tsv', tsv([[1, 'a']]))
|
||||
assert.deepEqual(resolveBase(file), { root: dir, base: file })
|
||||
})
|
||||
|
||||
test('resolveBase: a missing path and an empty path are different errors', () => {
|
||||
assert.throws(() => resolveBase(''), (err) => err.code === 'NO_PATH')
|
||||
assert.throws(() => resolveBase(path.join(tmpdir(), 'nope')), (err) => err.code === 'NOT_FOUND')
|
||||
})
|
||||
|
||||
test('resolveBase: a directory with no cliloc file names what it looked for', () => {
|
||||
assert.throws(() => resolveBase(tmpdir()), (err) => {
|
||||
assert.ok(err instanceof ClilocSourceError)
|
||||
assert.equal(err.code, 'NO_FILE')
|
||||
assert.match(err.message, /clilocs\.tsv/)
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
// ── Overlays ───────────────────────────────────────────────────────────────
|
||||
|
||||
test('listCustom: no overlay directory is normal, not an error', () => {
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[1, 'a']]))
|
||||
assert.deepEqual(listCustom(dir), [])
|
||||
})
|
||||
|
||||
test('listCustom: sorted, and only recognised extensions', () => {
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[1, 'a']]))
|
||||
write(dir, `${CUSTOM_DIR}/b.tsv`, tsv([[2, 'b']]))
|
||||
write(dir, `${CUSTOM_DIR}/a.csv`, tsv([[3, 'c']]))
|
||||
write(dir, `${CUSTOM_DIR}/notes.md`, 'ignore me')
|
||||
assert.deepEqual(listCustom(dir).map((f) => path.basename(f)), ['a.csv', 'b.tsv'])
|
||||
})
|
||||
|
||||
test('readSources: base first, then overlays, with root-relative labels', () => {
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[1, 'a']]))
|
||||
write(dir, `${CUSTOM_DIR}/shard.tsv`, tsv([[2, 'b']]))
|
||||
const { files } = readSources(dir)
|
||||
// Forward-slashed so the same directory read on Windows and Linux fingerprints
|
||||
// identically — otherwise every boot on one of them looks like a change.
|
||||
assert.deepEqual(files.map((f) => [f.label, f.kind]), [
|
||||
['clilocs.tsv', 'base'],
|
||||
['custom/shard.tsv', 'custom'],
|
||||
])
|
||||
})
|
||||
|
||||
// ── Merging ────────────────────────────────────────────────────────────────
|
||||
|
||||
test('readCliloc: an overlay ADDS ids the base never had', () => {
|
||||
// The whole point: shards add items, and those carry cliloc ids no stock
|
||||
// client table has.
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[1023721, 'quarter staff']]))
|
||||
write(dir, `${CUSTOM_DIR}/shard.tsv`, tsv([[1180001, 'Runic Gateway Sigil']]))
|
||||
const byNumber = new Map(readCliloc(dir).entries.map((e) => [e.number, e.text]))
|
||||
assert.equal(byNumber.get(1023721), 'quarter staff')
|
||||
assert.equal(byNumber.get(1180001), 'Runic Gateway Sigil')
|
||||
})
|
||||
|
||||
test('readCliloc: an overlay OVERRIDES a stock id', () => {
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[1023721, 'quarter staff']]))
|
||||
write(dir, `${CUSTOM_DIR}/shard.tsv`, tsv([[1023721, 'gnarled staff of testing']]))
|
||||
const byNumber = new Map(readCliloc(dir).entries.map((e) => [e.number, e.text]))
|
||||
assert.equal(byNumber.get(1023721), 'gnarled staff of testing')
|
||||
})
|
||||
|
||||
test('readCliloc: later overlays beat earlier ones, deterministically', () => {
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[7, 'base']]))
|
||||
write(dir, `${CUSTOM_DIR}/01-first.tsv`, tsv([[7, 'first']]))
|
||||
write(dir, `${CUSTOM_DIR}/02-second.tsv`, tsv([[7, 'second']]))
|
||||
const byNumber = new Map(readCliloc(dir).entries.map((e) => [e.number, e.text]))
|
||||
assert.equal(byNumber.get(7), 'second')
|
||||
})
|
||||
|
||||
test('readCliloc: reports what each source contributed', () => {
|
||||
// An operator who adds an overlay wants to see it took effect; "overrode: 0"
|
||||
// on a file meant to re-label stock items says it did not.
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[1, 'a'], [2, 'b']]))
|
||||
write(dir, `${CUSTOM_DIR}/shard.tsv`, tsv([[2, 'B!'], [3, 'c']]))
|
||||
const { source } = readCliloc(dir)
|
||||
assert.deepEqual(source.sources, [
|
||||
{ label: 'clilocs.tsv', kind: 'base', entries: 2, added: 2, overrode: 0 },
|
||||
{ label: 'custom/shard.tsv', kind: 'custom', entries: 2, added: 1, overrode: 1 },
|
||||
])
|
||||
})
|
||||
|
||||
test('readCliloc: a malformed overlay names the file it came from', () => {
|
||||
// "Which of my six overlay files is broken" is otherwise a guessing game.
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[1, 'a']]))
|
||||
write(dir, `${CUSTOM_DIR}/broken.tsv`, 'no separators here\nnor here\n')
|
||||
assert.throws(() => readCliloc(dir), (err) => {
|
||||
assert.equal(err.code, 'EMPTY')
|
||||
assert.match(err.message, /^custom\/broken\.tsv: /)
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
test('readCliloc: a compressed BASE is still rejected by name', () => {
|
||||
const dir = tmpdir()
|
||||
write(dir, 'cliloc.enu', Buffer.concat([Buffer.from([0xe8, 0x79, 0x67, 0x8e]), Buffer.alloc(32, 0x41)]))
|
||||
assert.throws(() => readCliloc(dir), (err) => err.code === 'COMPRESSED')
|
||||
})
|
||||
|
||||
// ── Drift over the SET ─────────────────────────────────────────────────────
|
||||
|
||||
test('hashSources: fingerprints every source, and counts the overlays', () => {
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[1, 'a']]))
|
||||
write(dir, `${CUSTOM_DIR}/shard.tsv`, tsv([[2, 'b']]))
|
||||
const fp = hashSources(dir)
|
||||
assert.deepEqual(Object.keys(fp.hashes).sort(), ['clilocs.tsv', 'custom/shard.tsv'])
|
||||
assert.equal(fp.customCount, 1)
|
||||
})
|
||||
|
||||
test('sameSources: adding or editing an overlay counts as drift', () => {
|
||||
const dir = tmpdir()
|
||||
write(dir, 'clilocs.tsv', tsv([[1, 'a']]))
|
||||
const before = hashSources(dir).hashes
|
||||
|
||||
write(dir, `${CUSTOM_DIR}/shard.tsv`, tsv([[2, 'b']]))
|
||||
const added = hashSources(dir).hashes
|
||||
assert.equal(sameSources(before, added), false, 'a new overlay is drift')
|
||||
|
||||
write(dir, `${CUSTOM_DIR}/shard.tsv`, tsv([[2, 'b changed']]))
|
||||
const edited = hashSources(dir).hashes
|
||||
assert.equal(sameSources(added, edited), false, 'an edited overlay is drift')
|
||||
assert.equal(sameSources(edited, hashSources(dir).hashes), true, 'an untouched set is not')
|
||||
})
|
||||
|
||||
test('missingSources: a vanished source is detected, an added one is not "missing"', () => {
|
||||
const loaded = { 'clilocs.tsv': 'aaa', 'custom/shard.tsv': 'bbb' }
|
||||
assert.deepEqual(missingSources({ 'clilocs.tsv': 'aaa' }, loaded), ['custom/shard.tsv'])
|
||||
assert.deepEqual(missingSources({ ...loaded, 'custom/new.tsv': 'ccc' }, loaded), [])
|
||||
// Nothing loaded yet (a first import) is not a vanished source.
|
||||
assert.deepEqual(missingSources({ 'clilocs.tsv': 'aaa' }, null), [])
|
||||
})
|
||||
Reference in New Issue
Block a user