Skip to content

Commit 2b42a5c

Browse files
committed
Add converter engine tests (32 tests)
Tests cover EuclidianDistanceEngine (initialization, toVector/fromVector round-trips, cross-method conversion, distance calculations) and HDSModelConverters integration (convertMethodToEvent, convertEventToMethod, convertMethodToMethod, loadPack, error handling).
1 parent 995e0e2 commit 2b42a5c

1 file changed

Lines changed: 274 additions & 0 deletions

File tree

tests/converterEngine.test.js

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
import { assert } from './test-utils/deps-node.js';
2+
import { EuclidianDistanceEngine, HDSModelConverters } from '../ts/index.ts';
3+
4+
// Load the cervical-fluid pack from data-model-draft dist
5+
import fs from 'fs';
6+
import path from 'path';
7+
import { fileURLToPath } from 'url';
8+
9+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
10+
// Resolve path to data-model-draft dist (sibling repo in _macro2)
11+
const macro2Root = path.resolve(__dirname, '../../..');
12+
const cervicalFluidPack = JSON.parse(
13+
fs.readFileSync(path.join(macro2Root, 'data-model-draft/data-model-draft/dist/converters/cervical-fluid/pack-latest.json'), 'utf-8')
14+
);
15+
const moodPack = JSON.parse(
16+
fs.readFileSync(path.join(macro2Root, 'data-model-draft/data-model-draft/dist/converters/mood/pack-latest.json'), 'utf-8')
17+
);
18+
19+
describe('[EDEX] EuclidianDistanceEngine', function () {
20+
let cfEngine;
21+
let moodEngine;
22+
23+
before(() => {
24+
cfEngine = new EuclidianDistanceEngine(cervicalFluidPack);
25+
moodEngine = new EuclidianDistanceEngine(moodPack);
26+
});
27+
28+
describe('[EDEI] Engine initialization', () => {
29+
it('[EDEI1] cervical-fluid engine loads 11 methods', () => {
30+
assert.strictEqual(cfEngine.methodIds.length, 11);
31+
});
32+
33+
it('[EDEI2] mood engine loads 2 methods', () => {
34+
assert.strictEqual(moodEngine.methodIds.length, 2);
35+
});
36+
37+
it('[EDEI3] cervical-fluid has 9 dimensions', () => {
38+
assert.strictEqual(cfEngine.dimensionNames.length, 9);
39+
});
40+
41+
it('[EDEI4] mood has 5 dimensions', () => {
42+
assert.strictEqual(moodEngine.dimensionNames.length, 5);
43+
});
44+
45+
it('[EDEI5] weights sum to 1.0', () => {
46+
let cfSum = 0;
47+
for (const d of cfEngine.dimensionNames) cfSum += cfEngine.weights[d];
48+
assert.ok(Math.abs(cfSum - 1.0) < 0.01);
49+
50+
let moodSum = 0;
51+
for (const d of moodEngine.dimensionNames) moodSum += moodEngine.weights[d];
52+
assert.ok(Math.abs(moodSum - 1.0) < 0.01);
53+
});
54+
});
55+
56+
describe('[EDEV] toVector / fromVector', () => {
57+
it('[EDEV1] appleHealth eggWhite → vector with high values', () => {
58+
const vec = cfEngine.toVector('appleHealth', 'eggWhite');
59+
assert.strictEqual(vec.threadiness, 1);
60+
assert.strictEqual(vec.stretchability, 1);
61+
assert.strictEqual(vec.lubricative, 1);
62+
});
63+
64+
it('[EDEV2] appleHealth dry → zero vector', () => {
65+
const vec = cfEngine.toVector('appleHealth', 'dry');
66+
let sum = 0;
67+
for (const d of cfEngine.dimensionNames) sum += vec[d];
68+
assert.strictEqual(sum, 0);
69+
});
70+
71+
it('[EDEV3] fromVector round-trips appleHealth observations', () => {
72+
for (const obs of ['dry', 'sticky', 'creamy', 'watery', 'eggWhite']) {
73+
const vec = cfEngine.toVector('appleHealth', obs);
74+
const result = cfEngine.fromVector('appleHealth', vec);
75+
assert.strictEqual(result.data, obs, `round-trip failed for ${obs}`);
76+
assert.strictEqual(result.matchDistance, 0, `distance should be 0 for exact match: ${obs}`);
77+
}
78+
});
79+
80+
it('[EDEV4] mira mood observations round-trip', () => {
81+
for (const obs of ['Happy', 'Sad', 'Normal', 'Excited']) {
82+
const vec = moodEngine.toVector('mira', obs);
83+
const result = moodEngine.fromVector('mira', vec);
84+
assert.strictEqual(result.data, obs, `round-trip failed for ${obs}`);
85+
}
86+
});
87+
88+
it('[EDEV5] case-insensitive matching for mira cervical-fluid', () => {
89+
const vec = cfEngine.toVector('mira', 'raw egg white');
90+
assert.strictEqual(vec.threadiness, 1);
91+
});
92+
93+
it('[EDEV6] unknown observation throws', () => {
94+
assert.throws(() => cfEngine.toVector('appleHealth', 'unknown'), /Unknown/);
95+
});
96+
97+
it('[EDEV7] unknown method throws', () => {
98+
assert.throws(() => cfEngine.toVector('nonexistent', 'dry'), /Unknown method/);
99+
});
100+
});
101+
102+
describe('[EDEC] Cross-method conversion', () => {
103+
it('[EDEC1] mira Raw Egg White → appleHealth eggWhite', () => {
104+
const result = cfEngine.convertMethodToMethod('mira', 'appleHealth', 'Raw Egg White');
105+
assert.strictEqual(result.data, 'eggWhite');
106+
assert.strictEqual(result.matchDistance, 0);
107+
});
108+
109+
it('[EDEC2] mira Creamy → appleHealth creamy', () => {
110+
const result = cfEngine.convertMethodToMethod('mira', 'appleHealth', 'Creamy');
111+
assert.strictEqual(result.data, 'creamy');
112+
});
113+
114+
it('[EDEC3] mira Dry → appleHealth dry', () => {
115+
const result = cfEngine.convertMethodToMethod('mira', 'appleHealth', 'Dry');
116+
assert.strictEqual(result.data, 'dry');
117+
});
118+
119+
it('[EDEC4] creighton 10KL → appleHealth eggWhite', () => {
120+
const result = cfEngine.convertMethodToMethod('creighton', 'appleHealth', '10KL');
121+
assert.strictEqual(result.data, 'eggWhite');
122+
});
123+
124+
it('[EDEC5] creighton 0 → appleHealth dry', () => {
125+
const result = cfEngine.convertMethodToMethod('creighton', 'appleHealth', '0');
126+
assert.strictEqual(result.data, 'dry');
127+
});
128+
129+
it('[EDEC6] mira Clumpy white → appleHealth sticky (pathological)', () => {
130+
const result = cfEngine.convertMethodToMethod('mira', 'appleHealth', 'Clumpy white');
131+
assert.ok(['dry', 'sticky'].includes(result.data), `expected dry or sticky, got ${result.data}`);
132+
});
133+
134+
it('[EDEC7] mood mira Happy → hds produces valid 5D vector', () => {
135+
const vec = moodEngine.toVector('mira', 'Happy');
136+
assert.strictEqual(vec.valence, 0.80);
137+
assert.strictEqual(vec.arousal, 0.60);
138+
const result = moodEngine.fromVector('hds', vec);
139+
// hds is assembly — result should be an object with 5 fields
140+
assert.ok(result.data, 'should have result data');
141+
});
142+
});
143+
144+
describe('[EDED] Distance calculations', () => {
145+
it('[EDED1] distance between identical vectors is 0', () => {
146+
const vec = cfEngine.toVector('appleHealth', 'creamy');
147+
assert.strictEqual(cfEngine.distance(vec, vec), 0);
148+
});
149+
150+
it('[EDED2] distance between dry and eggWhite is large', () => {
151+
const dry = cfEngine.toVector('appleHealth', 'dry');
152+
const egg = cfEngine.toVector('appleHealth', 'eggWhite');
153+
const d = cfEngine.distance(dry, egg);
154+
assert.ok(d > 0.5, `expected large distance, got ${d}`);
155+
});
156+
157+
it('[EDED3] distance between sticky and creamy is small', () => {
158+
const sticky = cfEngine.toVector('appleHealth', 'sticky');
159+
const creamy = cfEngine.toVector('appleHealth', 'creamy');
160+
const d = cfEngine.distance(sticky, creamy);
161+
assert.ok(d < 0.2, `expected small distance, got ${d}`);
162+
});
163+
164+
it('[EDED4] zeroVector has all zeros', () => {
165+
const z = cfEngine.zeroVector();
166+
for (const dim of cfEngine.dimensionNames) {
167+
assert.strictEqual(z[dim], 0);
168+
}
169+
});
170+
});
171+
});
172+
173+
describe('[MCVX] HDSModelConverters with loadPack', function () {
174+
let converters;
175+
176+
before(() => {
177+
// Create a minimal mock model
178+
const mockModel = {
179+
modelUrl: 'https://model.datasafe.dev/pack.json',
180+
modelData: {
181+
items: {
182+
'body-vulva-mucus-inspect': {
183+
key: 'body-vulva-mucus-inspect',
184+
streamId: 'body-vulva-mucus-inspect',
185+
eventType: 'vulva-mucus-inspect/9d-vector',
186+
type: 'convertible',
187+
'converter-engine': { key: 'euclidian-distance', version: 'v0', models: 'cervical-fluid' }
188+
},
189+
'wellbeing-mood': {
190+
key: 'wellbeing-mood',
191+
streamId: 'wellbeing-mood',
192+
eventType: 'mood/5d-vectors',
193+
type: 'convertible',
194+
'converter-engine': { key: 'euclidian-distance', version: 'v0', models: 'mood' }
195+
}
196+
},
197+
converters: {
198+
'cervical-fluid': { latestVersion: 'v0' },
199+
mood: { latestVersion: 'v0' }
200+
}
201+
}
202+
};
203+
204+
converters = new HDSModelConverters(mockModel);
205+
converters.loadPack(cervicalFluidPack);
206+
converters.loadPack(moodPack);
207+
});
208+
209+
it('[MCVX1] lists loaded item keys', () => {
210+
const keys = converters.loadedItemKeys.sort();
211+
assert.deepStrictEqual(keys, ['cervical-fluid', 'mood']);
212+
});
213+
214+
it('[MCVX2] getEngine returns loaded engines', () => {
215+
assert.ok(converters.getEngine('cervical-fluid'));
216+
assert.ok(converters.getEngine('mood'));
217+
assert.strictEqual(converters.getEngine('unknown'), undefined);
218+
});
219+
220+
it('[MCVX3] convertMethodToEvent produces valid event structure', async () => {
221+
const event = await converters.convertMethodToEvent('cervical-fluid', 'mira', 'Creamy');
222+
assert.strictEqual(event.type, 'vulva-mucus-inspect/9d-vector');
223+
assert.deepStrictEqual(event.streamIds, ['body-vulva-mucus-inspect']);
224+
assert.ok(event.content.data, 'should have data');
225+
assert.strictEqual(event.content.data.threadiness, 0.4);
226+
assert.ok(event.content.source, 'should have source');
227+
assert.strictEqual(event.content.source.key, 'mira');
228+
assert.strictEqual(event.content.source.sourceData, 'Creamy');
229+
assert.strictEqual(event.content.source.engineVersion, 'v0');
230+
});
231+
232+
it('[MCVX4] convertEventToMethod recovers source observation', async () => {
233+
const event = await converters.convertMethodToEvent('cervical-fluid', 'appleHealth', 'eggWhite');
234+
const result = await converters.convertEventToMethod(event, 'appleHealth');
235+
assert.strictEqual(result.data, 'eggWhite');
236+
assert.strictEqual(result.matchDistance, 0);
237+
});
238+
239+
it('[MCVX5] convertEventToMethod cross-method', async () => {
240+
const event = await converters.convertMethodToEvent('cervical-fluid', 'mira', 'Raw Egg White');
241+
const result = await converters.convertEventToMethod(event, 'appleHealth');
242+
assert.strictEqual(result.data, 'eggWhite');
243+
});
244+
245+
it('[MCVX6] convertMethodToMethod works', async () => {
246+
const result = await converters.convertMethodToMethod('mood', 'mira', 'hds', 'Happy');
247+
assert.ok(result.data, 'should have result');
248+
assert.ok(result.matchDistance >= 0, 'should have matchDistance');
249+
});
250+
251+
it('[MCVX7] convertMethodToEvent for mood produces valid event', async () => {
252+
const event = await converters.convertMethodToEvent('mood', 'mira', 'Sad');
253+
assert.strictEqual(event.type, 'mood/5d-vectors');
254+
assert.deepStrictEqual(event.streamIds, ['wellbeing-mood']);
255+
assert.strictEqual(event.content.data.valence, 0.15);
256+
assert.strictEqual(event.content.source.key, 'mira');
257+
assert.strictEqual(event.content.source.sourceData, 'Sad');
258+
});
259+
260+
it('[MCVX8] throws for unknown item key', async () => {
261+
try {
262+
await converters.convertMethodToEvent('unknown', 'mira', 'test');
263+
assert.fail('should have thrown');
264+
} catch (e) {
265+
assert.ok(e.message.includes('Unknown converter item key'));
266+
}
267+
});
268+
269+
it('[MCVX9] loadPack rejects unknown engine', () => {
270+
assert.throws(() => {
271+
converters.loadPack({ engine: 'neural-network', itemKey: 'test', methods: [] });
272+
}, /Unknown converter engine/);
273+
});
274+
});

0 commit comments

Comments
 (0)