import { test, describe, it, beforeEach, before } from 'node:test'; import assert from 'node:assert/strict'; import { ValueManager } from '../../src/core/ValueManager.js'; import { UnifiedKeyManager } from '../../src/core/UnifiedKeyManager.js'; import { ValueContext } from '../../src/authorization/ValueContext.js'; // Minimal mock RelationManager class MockRelationManager { constructor(relations) { this._relations = relations; } getAllValueRelationsFromSrc(entityId, relationName) { return this._relations.filter(r => r.src === entityId && r.rel === relationName); } getRawValueRelationsByName(relationName) { return this._relations.filter(r => r.rel === relationName); } getRawValueRelationsForLocalContext() { return []; // Not used in basic tests } } // Minimal mock Arbiter class MockArbiter { constructor(relations) { this.relationManager = new MockRelationManager(relations); this.nodeIdByKey = new Map(); this.keyByNodeId = new Map(); this.keyManager = new UnifiedKeyManager(); relations.forEach(r => { this.nodeIdByKey.set(r.srcKey, r.src); this.nodeIdByKey.set(r.dstKey, r.dst); this.keyByNodeId.set(r.src, r.srcKey); this.keyByNodeId.set(r.dst, r.dstKey); }); } resolveKey(id) { return this.keyByNodeId.get(id); } resolveNodeId(key) { return this.nodeIdByKey.get(key); } } describe('ValueManager & ValueContext', () => { let relations, arbiter, valueManager, valueContext; const now = Date.now(); beforeEach(() => { relations = [ { src: 1, dst: 2, rel: 'balance', value: 100, possibility: 1, reliability: 1, changed_last_at: now, srcKey: 'user:alice', dstKey: 'account:checking' }, { src: 1, dst: 3, rel: 'balance', value: 200, possibility: 0.8, reliability: 1, changed_last_at: now - 3600 * 1000, srcKey: 'user:alice', dstKey: 'account:savings' }, { src: 2, dst: 4, rel: 'price', value: 50, possibility: 1, reliability: 1, changed_last_at: now, srcKey: 'feature:basic', dstKey: 'price:basic' }, ]; arbiter = new MockArbiter(relations); valueManager = new ValueManager(arbiter); valueContext = new ValueContext(arbiter); }); it('extracts and blurs values correctly', () => { const rel = relations[0]; const blurred = valueManager.getBlurredValue(rel); assert.ok(blurred.interval); const EPS = 1e-6; const expectedMin = rel.value; const expectedMax = rel.value; assert.ok(Math.abs(blurred.interval.min - expectedMin) < EPS, `min: ${blurred.interval.min} vs ${expectedMin}`); assert.ok(Math.abs(blurred.interval.max - expectedMax) < EPS, `max: ${blurred.interval.max} vs ${expectedMax}`); assert.ok(Math.abs(blurred.possibility - 1) < EPS); }); it('applies decay and blur for old values', () => { const rel = relations[1]; // Use default config: decay should apply const blurred = valueManager.getBlurredValue(rel); assert.ok(blurred.interval); assert.ok(blurred.possibility === rel.possibility); assert.ok(blurred.interval.min === rel.value); assert.ok(blurred.interval.max === rel.value); }); it('caches values in ValueContext', () => { const vals1 = valueContext.getValues(1, 'balance'); const vals2 = valueContext.getValues(1, 'balance'); assert.deepEqual(vals1, vals2); assert.ok(valueContext.cacheHits > 0); }); it('aggregates values (max, min, sum, average)', () => { const max = valueContext.getAggregatedValue(1, 'balance', 'max'); assert.equal(max.value, 200); const min = valueContext.getAggregatedValue(1, 'balance', 'min'); assert.equal(min.value, 100); const sum = valueContext.getAggregatedValue(1, 'balance', 'sum'); assert.equal(sum.value, 300); const avg = valueContext.getAggregatedValue(1, 'balance', 'average'); assert.ok(Math.abs(avg.value - 150) < 1e-6); }); it('returns empty for missing values', () => { const vals = valueContext.getValues(99, 'balance'); assert.deepEqual(vals, []); const agg = valueContext.getAggregatedValue(99, 'balance', 'max'); assert.equal(agg.hasValue, false); assert.equal(agg.value, null); }); }); describe('OWAFusion', () => { let OWAFusion; before(async () => { ({ OWAFusion } = await import('../../src/utils/OWAFusion.js')); }); it('aggregates values with max, min, average, sum, custom', () => { const values = [10, 20, 30]; const metas = [{ label: 'a' }, { label: 'b' }, { label: 'c' }]; // Max const max = OWAFusion.fuseWithMeta(values, metas, null, 'max'); assert.ok(Math.abs(max.value - 30) < 1e-6); // Min const min = OWAFusion.fuseWithMeta(values, metas, null, 'min'); assert.ok(Math.abs(min.value - 10) < 1e-6); // Average const avg = OWAFusion.fuseWithMeta(values, metas, null, 'average'); assert.ok(Math.abs(avg.value - 20) < 1e-6); // Sum const sum = OWAFusion.fuseWithMeta(values, metas, null, 'sum', false); assert.ok(Math.abs(sum.value - 60) < 1e-6); // Custom weights (0.2, 0.3, 0.5) - OWA sorts values descending const custom = OWAFusion.fuseWithMeta(values, metas, [0.2, 0.3, 0.5], 'custom'); const sortedValues = [...values].sort((a, b) => b - a); // [30, 20, 10] const customWeights = [0.2, 0.3, 0.5]; const expected = sortedValues.reduce((sum, v, i) => sum + v * customWeights[i], 0); assert.ok(Math.abs(custom.value - expected) < 1e-6); }); it('aggregates intervals with max, min, sum, average', () => { const intervals = [ { min: 1, max: 2 }, { min: 3, max: 4 }, { min: 5, max: 6 } ]; const metas = [{ label: 'a' }, { label: 'b' }, { label: 'c' }]; // Max const max = OWAFusion.fuseIntervalsWithMeta(intervals, metas, null, 'max'); assert.ok(Math.abs(max.interval.max - 6) < 1e-6); // Min const min = OWAFusion.fuseIntervalsWithMeta(intervals, metas, null, 'min'); assert.ok(Math.abs(min.interval.min - 1) < 1e-6); // Sum const sum = OWAFusion.fuseIntervalsWithMeta(intervals, metas, null, 'sum'); assert.ok(Math.abs(sum.interval.min - 9) < 1e-6); assert.ok(Math.abs(sum.interval.max - 12) < 1e-6); // Average const avg = OWAFusion.fuseIntervalsWithMeta(intervals, metas, null, 'average'); assert.ok(Math.abs(avg.interval.min - 3) < 1e-6); assert.ok(Math.abs(avg.interval.max - 4) < 1e-6); // Custom weights (0.5, 0.3, 0.2) - OWA sorts intervals by midpoint descending const custom = OWAFusion.fuseIntervalsWithMeta(intervals, metas, [0.5, 0.3, 0.2], 'custom'); const sortedIntervals = [...intervals].sort((a, b) => ((b.min + b.max) / 2) - ((a.min + a.max) / 2)); const customWeights = [0.5, 0.3, 0.2]; const expectedMin = sortedIntervals.reduce((sum, iv, i) => sum + iv.min * customWeights[i], 0); const expectedMax = sortedIntervals.reduce((sum, iv, i) => sum + iv.max * customWeights[i], 0); assert.ok(Math.abs(custom.interval.min - expectedMin) < 1e-6); assert.ok(Math.abs(custom.interval.max - expectedMax) < 1e-6); }); });