import test from 'node:test'; import assert from 'node:assert/strict'; import { Broker } from '../src/broker.mjs'; import { BUSINESSES, broker } from '../../cli/tests/helpers.mjs'; import { views } from '../src/views.mjs'; const call = (s, cap, verb, args = {}) => s.b.request(cap, { verb, args }); const send = (s, body, to = 'human') => call(s, s.coder, 'message.send', { to, body, class: 'INFO' }).id; test('messages pages all human messages newest first without writes; read state is history', t => { const s = broker(t), ids = []; for (let i = 0; i < 205; i++) ids.push(send(s, `note ${i}`)); send(s, 'role only', 'pm'); const before = s.store.all('SELECT * FROM deliveries'); const first = s.read('messages'); assert.equal(first.messages.length, 50); assert.equal(first.total, 205); assert.equal(first.unread, 205); assert.deepEqual(first.messages.map(m => m.id), ids.slice(-50).reverse()); assert.ok(first.messages.every(m => m.read === false && m.in_reply_to === null)); assert.equal(first.next, ids[155]); const second = s.read('messages', { limit: 200, after: first.next }); assert.deepEqual(second.messages.map(m => m.id), ids.slice(0,155).reverse()); assert.equal(second.next, null); assert.equal(s.read('messages', {limit:200}).messages.length,200); assert.deepEqual(s.store.all('SELECT * FROM deliveries'), before); assert.deepEqual(call(s,s.human,'messages'), first); const got = call(s,s.human,'message.receive'); call(s,s.human,'message.read',{id:got[0].id}); const read = s.read('messages', {limit:200, after:ids[1]}); assert.equal(read.messages[0].read,true); assert.equal(read.unread,204); const delivered = s.read('messages', {limit:200, after:ids[2]}).messages.find(m => m.id === got[1].id); assert.equal(delivered.read,false,'delivered without a read receipt remains unread'); }); test('messages refuses role callers, invalid paging and cursors outside this human feed', t => { const s=broker(t); send(s,'hello'); const other=send(s,'role','pm'); for (const args of [{limit:0},{limit:201},{limit:1.5},{limit:'2'},{after:''},{after:3},{extra:true}]) assert.throws(()=>s.read('messages',args),{code:'invalid-request'}); for (const after of ['missing',other]) assert.throws(()=>s.read('messages',{after}),{code:'message-not-found'}); assert.throws(()=>call(s,s.coder,'messages'),{code:'human-required'}); const cap=s.b.bindReader({business:'demo'}); assert.throws(()=>call(s,cap,'message.receive'),{code:'read-only'}); }); test('views messages passes only its paging arguments through', async()=>{ const calls=[]; const v=views({call:async(...args)=>(calls.push(args),{messages:[],next:null})}); await v.messages({limit:2,after:'m-1'}); await v.messages(); assert.deepEqual(calls,[['messages',{limit:2,after:'m-1'}],['messages',{}]]); }); test('messages isolates businesses even with a valid foreign cursor', t => { const s=broker(t); // A second broker shares this test's store but binds a separate business. const otherBroker=new Broker({store:s.store,businesses:{other:{...BUSINESSES.demo,id:'other'}}}); const agent=otherBroker.bindLaunch({business:'other',role:'coder',run:'other-run',harness:'pi'}); otherBroker.request(agent,{verb:'role.claim'}); const foreign=otherBroker.request(agent,{verb:'message.send',args:{to:'human',body:'foreign'}}).id; const own=send(s,'own'); assert.deepEqual(s.read('messages').messages.map(m=>m.id),[own]); assert.throws(()=>s.read('messages',{after:foreign}),{code:'message-not-found'}); const reader=otherBroker.bindReader({business:'other'}); assert.deepEqual(otherBroker.request(reader,{verb:'messages'}).messages.map(m=>m.id),[foreign]); });