import test from 'node:test'; import assert from 'node:assert/strict'; import { quoteNexusAdvisors, createNexusAdvisors, validateNexusAdvisors, type NexusAdvisorContext, type NexusAdvisorReceipt, } from '../game/nexus-advisors'; const reload = (value: T): T => JSON.parse(JSON.stringify(value)) as T; function context(): NexusAdvisorContext { return { advanced: true, turn: 3, storm: 18, order: ['b', 'g', 'r'], players: [ { id: 'b', faction: 'beneGesserit', ally: null, forces: { 'hagga_basin:12': 2, 'hagga_basin:13': 1, 'arrakeen:10': 2 }, advisors: { hagga_basin: { lockedTurn: 2 }, arrakeen: {} }, }, { id: 'g', faction: 'guild', ally: null, forces: { 'hagga_basin:12': 1, 'arrakeen:10': 1 }, }, { id: 'r', faction: 'richese', ally: null, forces: {} }, ], }; } void test('one territory selection includes all physical advisor sectors and permits several territory groups as one immutable receipt', () => { const c = context(), before = reload(c), quote = quoteNexusAdvisors(c, 'b'); assert.deepEqual( quote.territories .map((t) => [t.territory, t.count, t.blocked]) .sort((one, two) => String(one[0]).localeCompare(String(two[0]))), [ ['arrakeen', 2, null], ['hagga_basin', 3, null], ], ); const receipt = createNexusAdvisors(c, 'b', 'advisor-1', [ 'hagga_basin', 'arrakeen', ]); assert.deepEqual(receipt.selections[0], { territory: 'hagga_basin', count: 3, locations: [ { location: 'hagga_basin:12', count: 2 }, { location: 'hagga_basin:13', count: 1 }, ], noField: null, stance: { lockedTurn: 2 }, }); assert.deepEqual(c, before); validateNexusAdvisors(reload(c), reload(receipt), true); receipt.selections[0].locations[0].count = 0; assert.deepEqual(c, before); }); void test('Basic and automatically released lone advisors yield no choice; Homeworld stances are not selected', () => { const c = context(); assert.deepEqual(quoteNexusAdvisors({ ...c, advanced: false }, 'b'), { territories: [], }); c.players[1].forces = {}; c.players[0].advisors!['homeworld:beneGesserit'] = {}; assert.deepEqual(quoteNexusAdvisors(c, 'b'), { territories: [] }); assert.throws(() => createNexusAdvisors(c, 'b', 'x', ['homeworld:beneGesserit']), ); assert.throws(() => quoteNexusAdvisors(c, 'g')); }); void test('ordinary allied occupation and a third stronghold faction block conversion using public No-Field presence', () => { const allied = context(); allied.players[0].ally = 'g'; allied.players[1].ally = 'b'; assert.ok( quoteNexusAdvisors(allied, 'b').territories.every((t) => /ally/.test(t.blocked!), ), ); const full = context(); full.players[2].noField = { deployed: { location: { territory: 'arrakeen', sector: 10 } }, }; assert.match( quoteNexusAdvisors(full, 'b').territories.find( (t) => t.territory === 'arrakeen', )!.blocked!, /three occupying factions/, ); assert.equal( quoteNexusAdvisors(full, 'b').territories.find( (t) => t.territory === 'hagga_basin', )!.blocked, null, ); }); void test('the established Ecaz shared occupancy identity is preserved for capacity and allied entry', () => { const c = context(); c.players[1].faction = 'ecaz'; c.players[0].ally = 'g'; c.players[1].ally = 'b'; c.players[2].forces = { 'arrakeen:10': 1 }; assert.equal( quoteNexusAdvisors(c, 'b').territories.find( (t) => t.territory === 'arrakeen', )!.blocked, null, ); }); void test('public marker counts and locations are recorded without reading hidden No-Field denomination or other secrets', () => { const c = context(); c.players[0].noField = { deployed: { location: { territory: 'hagga_basin', sector: 12 } }, }; for (const player of c.players) for (const key of ['hand', 'spice', 'traitors', 'leaders']) Object.defineProperty(player, key, { get() { throw Error('Private field accessed'); }, }); const marker = c.players[0].noField!; for (const key of ['tokens', 'value', 'tokenId', 'lastShipped']) Object.defineProperty(marker, key, { get() { throw Error('Hidden marker accessed'); }, }); Object.defineProperty(marker.deployed!, 'tokenId', { get() { throw Error('Hidden identity accessed'); }, }); const receipt = createNexusAdvisors(c, 'b', 'private', ['hagga_basin']); assert.equal(receipt.selections[0].count, 4); assert.equal(receipt.selections[0].noField, 'hagga_basin:12'); assert.equal( receipt.selections[0].locations.reduce((sum, g) => sum + g.count, 0), 3, ); validateNexusAdvisors(c, receipt, true); const changed = reload(c); changed.players[0].noField = null; changed.players[0].forces = { 'hagga_basin:12': 3, 'hagga_basin:13': 1, 'arrakeen:10': 2, }; assert.equal( quoteNexusAdvisors(changed, 'b').territories.find( (t) => t.territory === 'hagga_basin', )!.count, 4, ); assert.throws(() => validateNexusAdvisors(changed, receipt, true)); }); void test('empty, duplicate, foreign and malformed selections reject immutably and current settlement rechecks exact counts and legality', () => { const c = context(), before = reload(c); for (const selected of [ [], ['arrakeen', 'arrakeen'], ['unknown'], ['arrakeen:10'], ]) assert.throws(() => createNexusAdvisors(c, 'b', 'x', selected)); assert.deepEqual(c, before); const receipt = createNexusAdvisors(c, 'b', 'x', ['arrakeen']); for (const change of [ (g: NexusAdvisorContext) => { g.players[0].forces = { 'arrakeen:10': 1 }; }, (g: NexusAdvisorContext) => { delete g.players[0].advisors!.arrakeen; }, (g: NexusAdvisorContext) => { g.players[0].advisors!.arrakeen.lockedTurn = 3; }, (g: NexusAdvisorContext) => { g.players[2].forces = { 'arrakeen:10': 1 }; }, ]) { const altered = reload(c); change(altered); assert.throws(() => validateNexusAdvisors(altered, receipt, true)); } }); void test('signed receipt detects altered counts, stances and ordering while completed history survives later movement and turns', () => { const c = context(), receipt = createNexusAdvisors(c, 'b', 'x', ['arrakeen', 'hagga_basin']); for (const change of [ (r: NexusAdvisorReceipt) => { r.selections.reverse(); }, (r: NexusAdvisorReceipt) => { r.selections[0].count++; }, (r: NexusAdvisorReceipt) => { r.selections[0].stance.lockedTurn = 1; }, (r: NexusAdvisorReceipt) => { r.selections[1].locations.reverse(); }, (r: NexusAdvisorReceipt) => { r.owner = 'g'; }, (r: NexusAdvisorReceipt) => { r.turn++; }, ]) { const bad = reload(receipt); change(bad); assert.throws(() => validateNexusAdvisors(c, bad, false)); } const later = reload(c); later.turn++; later.players[0].forces = {}; later.players[0].advisors = {}; validateNexusAdvisors(later, receipt, false); assert.throws(() => validateNexusAdvisors(later, receipt, true)); }); void test('ordinary in-memory undefined lock metadata normalizes into the same saved stance without mutating its source', () => { const c = context(); c.players[0].advisors!.arrakeen = { lockedTurn: undefined }; const receipt = createNexusAdvisors(c, 'b', 'undefined-lock', ['arrakeen']); assert.deepEqual(receipt.selections[0].stance, {}); assert.equal( Object.hasOwn(c.players[0].advisors!.arrakeen, 'lockedTurn'), true, ); validateNexusAdvisors(c, receipt, true); validateNexusAdvisors(reload(c), reload(receipt), true); });