84 lines
4.9 KiB
JavaScript
84 lines
4.9 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const Module = require('module');
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ad-log-test-'));
|
|
const originalLoad = Module._load;
|
|
let messages = [];
|
|
const vscode = {workspace:{workspaceFolders:[{uri:{fsPath:root}}], getConfiguration:()=>({get:(key, fallback)=>fallback})}};
|
|
Module._load = function(id, ...args) { return id === 'vscode' ? vscode : originalLoad.call(this, id, ...args); };
|
|
const {LogExplorer} = require('../out/logExplorer');
|
|
Module._load = originalLoad;
|
|
function explorer() { const e = new LogExplorer({}); const webview={postMessage:m=>messages.push(m)}; e.panel={webview}; e.activeWebview=webview; return e; }
|
|
function latest() { return messages.at(-1)?.entries || []; }
|
|
fs.mkdirSync(path.join(root,'logs'));
|
|
const file=path.join(root,'logs/appdaemon.log');
|
|
test('follows incomplete UTF-8 lines and multiline exceptions',()=>{
|
|
const e=explorer(); messages=[];
|
|
const first='27-09-26 10:12:13 ERROR app: Échec';
|
|
const bytes=Buffer.from(first+'\n'); const split=bytes.indexOf(Buffer.from('É'))+1;
|
|
fs.writeFileSync(file,bytes.subarray(0,split));e.poll();assert.equal(latest().length,0);
|
|
fs.appendFileSync(file,Buffer.concat([bytes.subarray(split),Buffer.from(' File "app.py", line 42\nRuntimeError: failed\n')]));e.poll();
|
|
assert.equal(latest().length,1);assert.match(latest()[0].message,/Échec\n File/);assert.match(latest()[0].message,/RuntimeError/);
|
|
});
|
|
test('detects same-sized inode replacement and truncation',()=>{
|
|
const e=explorer();messages=[];
|
|
fs.writeFileSync(file,'27-09-26 10:12:13 INFO app: before\n');e.poll();
|
|
fs.renameSync(file,file+'.old');fs.writeFileSync(file,'27-09-26 10:12:14 INFO app: after!\n');e.poll();
|
|
assert.ok(latest().some(x=>x.message==='after!'));
|
|
fs.writeFileSync(file,'27-09-26 10:12:15 INFO a: x\n');e.poll();assert.ok(latest().some(x=>x.message==='x'));
|
|
});
|
|
test('reads base text logs and ignores structured events by default',()=>{
|
|
const e=explorer();messages=[];
|
|
fs.writeFileSync(file,'27-09-26 10:12:13 ERROR app: failed\n27-09-26 10:12:14 INFO AppDaemon: AppDaemon is ready\n');
|
|
fs.writeFileSync(path.join(root,'logs/error.log'),'27-09-26 10:12:13 ERROR app: failed\n');
|
|
fs.writeFileSync(path.join(root,'logs/events.jsonl'),JSON.stringify({version:1,event_id:'trace-only',timestamp:'2026-09-27T08:12:14Z',level:'INFO',kind:'evaluation',message:'not a raw log',app:'app',cause_ids:[]})+'\n');e.poll();
|
|
assert.equal(latest().filter(e=>e.message==='failed').length,1);
|
|
assert.ok(latest().some(e=>e.app==='AppDaemon' && e.message==='AppDaemon is ready'));
|
|
assert.equal(latest().some(e=>e.event_id==='trace-only'),false);
|
|
});
|
|
test('bounds retained history',()=>{
|
|
const e=explorer();messages=[];
|
|
fs.writeFileSync(file,Array.from({length:10500},(_,i)=>`27-09-26 10:12:13 INFO app: message ${i}\n`).join(''));
|
|
e.poll();e.poll();assert.ok(e.entries.size<=10000);
|
|
});
|
|
process.on('exit',()=>fs.rmSync(root,{recursive:true,force:true}));
|
|
|
|
test('groups plain app.log lines between explicit initialization markers',()=>{
|
|
const e=explorer();messages=[];
|
|
fs.writeFileSync(file,[
|
|
'27-09-26 10:12:13 INFO kitchen: Initialization started [group=initialization:0123456789ab]',
|
|
'27-09-26 10:12:13 INFO kitchen: Setting up an output through app.log()',
|
|
'27-09-26 10:12:14 INFO kitchen: Configured helper [group=initialization:0123456789ab]',
|
|
'27-09-26 10:12:14 INFO kitchen: Initialization completed [group=initialization:0123456789ab]'
|
|
].join('\n')+'\n');e.poll();
|
|
const grouped=latest().filter(entry=>entry.app==='kitchen');
|
|
assert.equal(grouped.length,4);
|
|
assert.deepEqual(grouped.map(entry=>entry.initialization_id),Array(4).fill('0123456789ab'));
|
|
assert.equal(grouped[1].group_inferred,true);
|
|
assert.equal(grouped[2].group_inferred,undefined);
|
|
});
|
|
|
|
test('clears configured live files, keeps imported archives, and updates the log error count',()=>{
|
|
const counts=[];const e=new LogExplorer({},count=>counts.push(count));
|
|
messages=[];const webview={postMessage:m=>messages.push(m)};e.panel={webview};e.activeWebview=webview;
|
|
const errorFile=path.join(root,'logs/error.log');
|
|
const archive=path.join(root,'logs/history.log.1');
|
|
fs.writeFileSync(file,'27-09-26 10:12:13 ERROR app: failed\n');
|
|
fs.writeFileSync(errorFile,'27-09-26 10:12:13 ERROR app: failed\n');
|
|
fs.writeFileSync(archive,'27-09-26 10:12:14 INFO app: archived\n');
|
|
e.imported.add(archive);e.poll();
|
|
assert.equal(counts.at(-1),1);
|
|
e.clearLogs();
|
|
assert.equal(fs.statSync(file).size,0);
|
|
assert.equal(fs.statSync(errorFile).size,0);
|
|
assert.match(fs.readFileSync(archive,'utf8'),/archived/);
|
|
assert.equal(counts.at(-1),0);
|
|
assert.deepEqual(messages.filter(message=>message.type==='entries').at(-1).entries,[]);
|
|
fs.appendFileSync(file,'27-09-26 10:12:15 CRITICAL app: new failure\n');e.poll();
|
|
assert.equal(counts.at(-1),1);
|
|
assert.equal(latest()[0].message,'new failure');
|
|
});
|