// Fixture tests for pagination, decryption errors, and plaintext redaction. const assert = require('node:assert/strict'); const vm = require('node:vm'); const fs = require('node:fs'); const probe = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); const rows = ['a','b','c','d','e'].map(id => ({id, body:'text-'+id, type:'chat', t:123, invisible:id==='b'})); const events = []; function req(result) { const r = {}; queueMicrotask(() => {r.result=result; r.onsuccess();}); return r; } const store = { count:()=>req(rows.length), openKeyCursor(range,direction) { const selected=rows.filter(r=>!range || ((range.lower===undefined || r.id>range.lower) && r.id<=range.upper)); if(direction==='prev')selected.reverse(); const request={};let index=0; function next(){queueMicrotask(()=>{ request.result=indexn==='message'},transaction:(name,mode)=>{ assert.equal(name,'message');assert.equal(mode,'readonly');return {objectStore:()=>store}; },close:()=>events.push('close')}; const modules={ WAWebSchemaMessage:{getMessageTable:()=>({get:async key=>{ events.push('decrypt:'+key); if(key==='c')throw Error('simulated unavailable decryption key'); return {...rows.find(r=>r.id===key)}; }})}, WAWebDBMessageSerialization:{messageFromDbRow:row=>{ events.push('decode:'+row.id); return {...row,id:{id:row.id,remote:'test-chat',fromMe:false}}; }} }; async function run(options){return vm.runInNewContext(probe.replace('__OPTIONS__',JSON.stringify(options)),{ location:{origin:'https://web.whatsapp.com'}, require:n=>modules[n], indexedDB:{databases:async()=>[{name:'model-storage'}],open:()=>req(db)}, IDBKeyRange:{upperBound:upper=>({upper}),bound:(lower,upper,openLower,openUpper)=>{ assert.equal(openLower,true);assert.equal(openUpper,false);return {lower,upper}; }} });} (async()=>{ const bounds=await run({mode:'bounds'});assert.equal(bounds.count,5);assert.equal(bounds.upper,'e'); let after=null,all=[]; while(after!=='e'){ const page=await run({mode:'batch',upper:'e',after,batch:2,redact:false}); assert.ok(page.keys.length<=2);all.push(...page.messages);after=page.last; } assert.deepEqual(all.map(m=>m.id),['a','b','c','d','e']); // UI-invisible b remains included. assert.equal(all[2].error,'row_decryption_or_decoding_failed'); assert.equal(all[1].body,'text-b'); const redacted=await run({mode:'batch',upper:'b',after:null,batch:2,redact:true}); assert.ok(redacted.messages.every(m=>!('body' in m)&&!('caption' in m))); assert.equal(redacted.messages[0].body_length,6); assert.ok(events.indexOf('decrypt:a'){console.error(error);process.exitCode=1;});