Add SystemOne classification support and release version 4.0.0
This commit is contained in:
parent
76195f9a92
commit
9a5404cb62
16 changed files with 923 additions and 579 deletions
80
tests/ruleExecution.test.mjs
Normal file
80
tests/ruleExecution.test.mjs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import vm from 'node:vm';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
// Run the actual background rule/queue functions with Thunderbird services mocked.
|
||||
const source = await readFile(new URL('../background.js', import.meta.url), 'utf8');
|
||||
const functions = source.slice(source.indexOf('async function processMessage('), source.indexOf('async function clearCacheForMessages('));
|
||||
function runner({ fail = false, changeSettings = false } = {}) {
|
||||
const actions = [], evaluated = [], errors = [];
|
||||
const rules = [
|
||||
{ criterion: 'first', decisionThreshold: 0.8, actions: [{ type: 'flag', flagged: true }], stopProcessing: true },
|
||||
{ criterion: 'second', actions: [{ type: 'read', read: true }] }
|
||||
];
|
||||
let currentConfig = { apiType: 'systemone', inputCharacterLimit: 5, decisionThreshold: 0.5 };
|
||||
const context = vm.createContext({
|
||||
structuredClone, Date, Error,
|
||||
aiRules: rules, queue: Promise.resolve(), queuedCount: 0, processing: false,
|
||||
currentStart: 0, tokenReduction: true, showDebugTab: false, timingStats: {},
|
||||
updateActionIcon() {}, updateTimingStats() {}, showTransientIcon() {},
|
||||
buildEmailText() { return 'email contents'; }, ICONS: { circle: 'circle' },
|
||||
logger: { aiLog() {} }, recordError(...args) { errors.push(args); },
|
||||
ERROR_NOTIFICATION_ID: 'error',
|
||||
storage: { local: { async set() {} } },
|
||||
messenger: {
|
||||
messages: {
|
||||
async getFull() { return {}; },
|
||||
async get() {
|
||||
if (changeSettings) {
|
||||
rules[0].decisionThreshold = 0.1;
|
||||
currentConfig = { apiType: 'chat-completions', decisionThreshold: 0.1 };
|
||||
}
|
||||
return { tags: [], folder: { accountId: 'account' } };
|
||||
},
|
||||
async update(id, action) { actions.push({ id, ...action }); }
|
||||
}, identities: { async list() { return []; } }
|
||||
},
|
||||
browser: { runtime: { getURL: x => x }, notifications: { async create() {} } },
|
||||
AiClassifier: {
|
||||
getConfig() { return structuredClone(currentConfig); },
|
||||
async buildCacheKey(id, criterion) { return `${id}:${criterion}`; },
|
||||
async classify(text, criterion, options) {
|
||||
evaluated.push({ text, criterion, ...options });
|
||||
if (fail && options.cacheKey.startsWith('1:')) throw new Error('inference failed');
|
||||
return { matched: true };
|
||||
}
|
||||
}
|
||||
});
|
||||
vm.runInContext(functions, context);
|
||||
return { context, actions, evaluated, errors };
|
||||
}
|
||||
|
||||
test('failure stops remaining rules for one email, next queued email still acts', async () => {
|
||||
const { context, actions, evaluated, errors } = runner({ fail: true });
|
||||
await vm.runInContext('applyAiRules([1, 2])', context);
|
||||
assert.deepEqual(evaluated.map(x => x.cacheKey), ['1:first', '2:first']);
|
||||
assert.deepEqual(actions, [{ id: 2, flagged: true }]);
|
||||
assert.equal(errors.length, 1);
|
||||
assert.equal(context.processing, false);
|
||||
assert.equal(context.queuedCount, 0);
|
||||
});
|
||||
|
||||
test('settings and rules remain fixed during an email; reduction uses characters', async () => {
|
||||
const { context, evaluated, actions } = runner({ changeSettings: true });
|
||||
await vm.runInContext('applyAiRules([1])', context);
|
||||
assert.equal(evaluated[0].text, 'email');
|
||||
assert.equal(evaluated[0].threshold, 0.8);
|
||||
assert.equal(evaluated[0].snapshot.apiType, 'systemone');
|
||||
assert.equal(evaluated.length, 1); // stop after first match
|
||||
assert.equal(actions.length, 1);
|
||||
});
|
||||
|
||||
test('inherited thresholds and eligibility preserve ordered rule processing', async () => {
|
||||
const { context, evaluated, actions } = runner();
|
||||
context.aiRules[0].enabled = false;
|
||||
await vm.runInContext('applyAiRules([1])', context);
|
||||
assert.equal(evaluated[0].criterion, 'second');
|
||||
assert.equal(evaluated[0].threshold, 0.5);
|
||||
assert.deepEqual(actions, [{ id: 1, read: true }]);
|
||||
});
|
||||
Loading…
Reference in a new issue