Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
235 lines
6.0 KiB
JavaScript
235 lines
6.0 KiB
JavaScript
'use strict';
|
|
|
|
var common = require('./common');
|
|
var EventEmitter = require('../').EventEmitter;
|
|
var once = require('../').once;
|
|
var has = require('has');
|
|
var assert = require('assert');
|
|
|
|
function Event(type) {
|
|
this.type = type;
|
|
}
|
|
|
|
function EventTargetMock() {
|
|
this.events = {};
|
|
|
|
this.addEventListener = common.mustCall(this.addEventListener);
|
|
this.removeEventListener = common.mustCall(this.removeEventListener);
|
|
}
|
|
|
|
EventTargetMock.prototype.addEventListener = function addEventListener(name, listener, options) {
|
|
if (!(name in this.events)) {
|
|
this.events[name] = { listeners: [], options: options || {} }
|
|
}
|
|
this.events[name].listeners.push(listener);
|
|
};
|
|
|
|
EventTargetMock.prototype.removeEventListener = function removeEventListener(name, callback) {
|
|
if (!(name in this.events)) {
|
|
return;
|
|
}
|
|
var event = this.events[name];
|
|
var stack = event.listeners;
|
|
|
|
for (var i = 0, l = stack.length; i < l; i++) {
|
|
if (stack[i] === callback) {
|
|
stack.splice(i, 1);
|
|
if (stack.length === 0) {
|
|
delete this.events[name];
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
EventTargetMock.prototype.dispatchEvent = function dispatchEvent(arg) {
|
|
if (!(arg.type in this.events)) {
|
|
return true;
|
|
}
|
|
|
|
var event = this.events[arg.type];
|
|
var stack = event.listeners.slice();
|
|
|
|
for (var i = 0, l = stack.length; i < l; i++) {
|
|
stack[i].call(null, arg);
|
|
if (event.options.once) {
|
|
this.removeEventListener(arg.type, stack[i]);
|
|
}
|
|
}
|
|
return !arg.defaultPrevented;
|
|
};
|
|
|
|
function onceAnEvent() {
|
|
var ee = new EventEmitter();
|
|
|
|
process.nextTick(function () {
|
|
ee.emit('myevent', 42);
|
|
});
|
|
|
|
return once(ee, 'myevent').then(function (args) {
|
|
var value = args[0]
|
|
assert.strictEqual(value, 42);
|
|
assert.strictEqual(ee.listenerCount('error'), 0);
|
|
assert.strictEqual(ee.listenerCount('myevent'), 0);
|
|
});
|
|
}
|
|
|
|
function onceAnEventWithTwoArgs() {
|
|
var ee = new EventEmitter();
|
|
|
|
process.nextTick(function () {
|
|
ee.emit('myevent', 42, 24);
|
|
});
|
|
|
|
return once(ee, 'myevent').then(function (value) {
|
|
assert.strictEqual(value.length, 2);
|
|
assert.strictEqual(value[0], 42);
|
|
assert.strictEqual(value[1], 24);
|
|
});
|
|
}
|
|
|
|
function catchesErrors() {
|
|
var ee = new EventEmitter();
|
|
|
|
var expected = new Error('kaboom');
|
|
var err;
|
|
process.nextTick(function () {
|
|
ee.emit('error', expected);
|
|
});
|
|
|
|
return once(ee, 'myevent').then(function () {
|
|
throw new Error('should reject')
|
|
}, function (err) {
|
|
assert.strictEqual(err, expected);
|
|
assert.strictEqual(ee.listenerCount('error'), 0);
|
|
assert.strictEqual(ee.listenerCount('myevent'), 0);
|
|
});
|
|
}
|
|
|
|
function stopListeningAfterCatchingError() {
|
|
var ee = new EventEmitter();
|
|
|
|
var expected = new Error('kaboom');
|
|
var err;
|
|
process.nextTick(function () {
|
|
ee.emit('error', expected);
|
|
ee.emit('myevent', 42, 24);
|
|
});
|
|
|
|
// process.on('multipleResolves', common.mustNotCall());
|
|
|
|
return once(ee, 'myevent').then(common.mustNotCall, function (err) {
|
|
// process.removeAllListeners('multipleResolves');
|
|
assert.strictEqual(err, expected);
|
|
assert.strictEqual(ee.listenerCount('error'), 0);
|
|
assert.strictEqual(ee.listenerCount('myevent'), 0);
|
|
});
|
|
}
|
|
|
|
function onceError() {
|
|
var ee = new EventEmitter();
|
|
|
|
var expected = new Error('kaboom');
|
|
process.nextTick(function () {
|
|
ee.emit('error', expected);
|
|
});
|
|
|
|
var promise = once(ee, 'error');
|
|
assert.strictEqual(ee.listenerCount('error'), 1);
|
|
return promise.then(function (args) {
|
|
var err = args[0]
|
|
assert.strictEqual(err, expected);
|
|
assert.strictEqual(ee.listenerCount('error'), 0);
|
|
assert.strictEqual(ee.listenerCount('myevent'), 0);
|
|
});
|
|
}
|
|
|
|
function onceWithEventTarget() {
|
|
var et = new EventTargetMock();
|
|
var event = new Event('myevent');
|
|
process.nextTick(function () {
|
|
et.dispatchEvent(event);
|
|
});
|
|
return once(et, 'myevent').then(function (args) {
|
|
var value = args[0];
|
|
assert.strictEqual(value, event);
|
|
assert.strictEqual(has(et.events, 'myevent'), false);
|
|
});
|
|
}
|
|
|
|
function onceWithEventTargetError() {
|
|
var et = new EventTargetMock();
|
|
var error = new Event('error');
|
|
process.nextTick(function () {
|
|
et.dispatchEvent(error);
|
|
});
|
|
return once(et, 'error').then(function (args) {
|
|
var err = args[0];
|
|
assert.strictEqual(err, error);
|
|
assert.strictEqual(has(et.events, 'error'), false);
|
|
});
|
|
}
|
|
|
|
function prioritizesEventEmitter() {
|
|
var ee = new EventEmitter();
|
|
ee.addEventListener = assert.fail;
|
|
ee.removeAllListeners = assert.fail;
|
|
process.nextTick(function () {
|
|
ee.emit('foo');
|
|
});
|
|
return once(ee, 'foo');
|
|
}
|
|
|
|
var allTests = [
|
|
onceAnEvent(),
|
|
onceAnEventWithTwoArgs(),
|
|
catchesErrors(),
|
|
stopListeningAfterCatchingError(),
|
|
onceError(),
|
|
onceWithEventTarget(),
|
|
onceWithEventTargetError(),
|
|
prioritizesEventEmitter()
|
|
];
|
|
|
|
var hasBrowserEventTarget = false;
|
|
try {
|
|
hasBrowserEventTarget = typeof (new window.EventTarget().addEventListener) === 'function' &&
|
|
new window.Event('xyz').type === 'xyz';
|
|
} catch (err) {}
|
|
|
|
if (hasBrowserEventTarget) {
|
|
var onceWithBrowserEventTarget = function onceWithBrowserEventTarget() {
|
|
var et = new window.EventTarget();
|
|
var event = new window.Event('myevent');
|
|
process.nextTick(function () {
|
|
et.dispatchEvent(event);
|
|
});
|
|
return once(et, 'myevent').then(function (args) {
|
|
var value = args[0];
|
|
assert.strictEqual(value, event);
|
|
assert.strictEqual(has(et.events, 'myevent'), false);
|
|
});
|
|
}
|
|
|
|
var onceWithBrowserEventTargetError = function onceWithBrowserEventTargetError() {
|
|
var et = new window.EventTarget();
|
|
var error = new window.Event('error');
|
|
process.nextTick(function () {
|
|
et.dispatchEvent(error);
|
|
});
|
|
return once(et, 'error').then(function (args) {
|
|
var err = args[0];
|
|
assert.strictEqual(err, error);
|
|
assert.strictEqual(has(et.events, 'error'), false);
|
|
});
|
|
}
|
|
|
|
common.test.comment('Testing with browser built-in EventTarget');
|
|
allTests.push([
|
|
onceWithBrowserEventTarget(),
|
|
onceWithBrowserEventTargetError()
|
|
]);
|
|
}
|
|
|
|
module.exports = Promise.all(allTests);
|