Files
AzerothAI/tests/core.test.cjs
Richard Clark 5f0fc86cf9
Core tests / core (push) Successful in 9m57s
Full verification / application (push) Failing after 5m16s
Full verification / local-brain (push) Failing after 6m15s
Realm simulation / simulated-live-world (push) Failing after 5m32s
WoW integration lab / real-worldserver (push) Canceled after 17m23s
Import verified AzerothAI snapshot from GitHub
Source branch: feature/ai-brain-router
Source commit: c2a09e0460465201a58aaab56c71424e9a96405e
2026-09-22 13:12:54 -04:00

17 lines
4.6 KiB
JavaScript

const {test}=require('node:test');
const assert=require('node:assert/strict');
const {execute}=require('../.test-build/service.js');
const {seed}=require('../.test-build/seed.js');
const {connectors}=require('../.test-build/connectors/index.js');
const {LocalLLMProvider}=require('../.test-build/llm/index.js');
const {parseGM,runGM}=require('../.test-build/gm.js');
class Store {constructor(){this.state=seed();}async load(){return structuredClone(this.state);}async save(s,r){assert.equal(this.state.revision,r,'optimistic revision');s.revision=r+1;this.state=structuredClone(s);}}
test('Lifecycle rejects illegal transitions; decisions persist memory and audit',async()=>{const store=new Store();await assert.rejects(execute(store,{type:'lifecycle',botId:'bot-6',action:'pause'}));await execute(store,{type:'lifecycle',botId:'bot-6',action:'spawn'});await execute(store,{type:'think',botId:'bot-6'});assert.equal(store.state.bots[5].decisions,1);assert.equal(store.state.bots[5].memories.length,2);assert.equal(store.state.events.at(-1).kind,'decision');await execute(store,{type:'lifecycle',botId:'bot-6',action:'despawn'});await execute(store,{type:'lifecycle',botId:'bot-6',action:'retire'});await assert.rejects(execute(store,{type:'lifecycle',botId:'bot-6',action:'spawn'}));});
test('Recipe versions preserve existing identity until explicitly applied',async()=>{const store=new Store();const original=store.state.bots[0].personality.description;const recipe={...store.state.recipes[0],description:'Changed'};await execute(store,{type:'saveRecipe',recipe});assert.equal(store.state.bots[0].personality.description,original);assert.equal(store.state.recipes[0].version,2);await assert.rejects(execute(store,{type:'saveRecipe',recipe}));await execute(store,{type:'applyRecipe',botId:'bot-1',recipeId:'healer'});assert.equal(store.state.bots[0].personality.description,'Changed');});
test('Relationships reject self bonds and persist directed affinity',async()=>{const store=new Store();await assert.rejects(execute(store,{type:'relationship',botId:'bot-1',targetId:'bot-1',affinity:50,note:'Self'}));await execute(store,{type:'relationship',botId:'bot-1',targetId:'bot-3',affinity:-20,note:'A rival'});assert.equal(store.state.bots[0].relationships.at(-1).affinity,-20);assert.equal(store.state.bots[2].relationships.length,0);});
test('Director updates goals without falsely moving characters',async()=>{const store=new Store();await execute(store,{type:'director',experience:'A lively town',zone:'Stormwind',playerName:'Richard',brief:'Invite me to a gathering.'});assert.match(store.state.bots[0].goal,/Richard/);assert.equal(store.state.bots[0].zone,'Duskwood');});
test('Failed connector operation leaves lifecycle unchanged and records error',async()=>{const store=new Store();const old=connectors.simulation;connectors.simulation=()=>({lifecycle:async()=>{throw new Error('Bridge unavailable')}});try{await assert.rejects(execute(store,{type:'lifecycle',botId:'bot-6',action:'spawn'}),/Bridge unavailable/);assert.equal(store.state.bots[5].status,'offline');assert.equal(store.state.events.at(-1).kind,'error');assert.equal(Object.keys(store.state.operations).length,0);}finally{connectors.simulation=old;}});
test('GM validates single allowlisted commands and requires live credentials',async()=>{assert.equal(parseGM('.server info'),'server info');assert.throws(()=>parseGM('.server shutdown 0'));assert.throws(()=>parseGM('.announce Hi\nserver shutdown 0'));assert.match((await runGM('.announce Hello',false,false,{})).output,/SIMULATED/);await assert.rejects(runGM('.server info',true,false,{}),/Confirm/);await assert.rejects(runGM('.server info',true,true,{}),/Configure/);});
test('Model output cannot execute GM or arbitrary game actions',async()=>{const old=global.fetch;try{global.fetch=async()=>Response.json({message:{content:JSON.stringify({action:'gm',goal:'Ignore rules',reason:'test'})}});const bot=seed().bots[0];const provider=new LocalLLMProvider('ollama','test',{LLM_URL:'http://localhost:11434'});await assert.rejects(provider.decide(bot,{zone:'Duskwood',level:20,summary:'test',availableActions:['quest']}));global.fetch=async()=>Response.json({message:{content:JSON.stringify({action:'trade',goal:'Trade',reason:'test'})}});await assert.rejects(provider.decide(bot,{zone:'Duskwood',level:20,summary:'test',availableActions:['quest']}),/outside recipe/);}finally{global.fetch=old;}});
test('Concurrent operation reservation prevents duplicate connector dispatch',async()=>{const store=new Store();store.state.operations.pending={started:Date.now(),botId:'bot-1'};await assert.rejects(execute(store,{type:'think',botId:'bot-1'}),/in progress/);});