'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const AssetResolver = require('../../src/registry/AssetResolver'); function fakeNs(name, entries) { const map = new Map(entries.map(([k, v]) => [String(k).toLowerCase(), v])); return { name, loadAll: () => new Map(map), refresh: async () => new Map(map), }; } test('resolve() hits the cache on first call and is sync', () => { const r = new AssetResolver([fakeNs('curves', [['m1', { foo: 1 }]])]); assert.deepEqual(r.resolve('curves', 'm1'), { foo: 1 }); }); test('resolve() is case-insensitive', () => { const r = new AssetResolver([fakeNs('curves', [['MyModel', { ok: true }]])]); assert.deepEqual(r.resolve('curves', 'mymodel'), { ok: true }); assert.deepEqual(r.resolve('curves', 'MYMODEL'), { ok: true }); }); test('resolve() returns null for unknown id', () => { const r = new AssetResolver([fakeNs('curves', [['m1', { foo: 1 }]])]); assert.equal(r.resolve('curves', 'm999'), null); assert.equal(r.resolve('curves', ''), null); assert.equal(r.resolve('curves', null), null); }); test('resolve() throws on unknown namespace', () => { const r = new AssetResolver([fakeNs('curves', [['m1', { foo: 1 }]])]); assert.throws(() => r.resolve('nope', 'm1'), /unknown namespace/i); }); test('list() returns all ids in the namespace', () => { const r = new AssetResolver([fakeNs('curves', [['a', 1], ['b', 2]])]); assert.deepEqual(r.list('curves').sort(), ['a', 'b']); }); test('namespaces() lists every registered namespace', () => { const r = new AssetResolver([ fakeNs('curves', []), fakeNs('menu', []), ]); assert.deepEqual(r.namespaces().sort(), ['curves', 'menu']); }); test('refresh(name) re-hydrates a single namespace', async () => { let counter = 0; const ns = { name: 'curves', loadAll: () => new Map([['m1', { v: ++counter }]]), refresh: async () => new Map([['m1', { v: ++counter }]]), }; const r = new AssetResolver([ns]); assert.deepEqual(r.resolve('curves', 'm1'), { v: 1 }); await r.refresh('curves'); assert.deepEqual(r.resolve('curves', 'm1'), { v: 2 }); }); test('refresh() with no name re-hydrates every namespace', async () => { let cA = 0, cB = 0; const r = new AssetResolver([ { name: 'a', loadAll: () => new Map([['x', { v: ++cA }]]), refresh: async () => new Map([['x', { v: ++cA }]]) }, { name: 'b', loadAll: () => new Map([['y', { v: ++cB }]]), refresh: async () => new Map([['y', { v: ++cB }]]) }, ]); r.resolve('a', 'x'); r.resolve('b', 'y'); await r.refresh(); assert.equal(r.resolve('a', 'x').v, 2); assert.equal(r.resolve('b', 'y').v, 2); }); test('constructor rejects malformed namespaces', () => { assert.throws(() => new AssetResolver([{ name: 'x' }]), /loadAll/); assert.throws(() => new AssetResolver([{ loadAll: () => {} }]), /name/); }); test('resolveAssetMetadata walks supplier→type→model and returns derived fields', () => { const r = new AssetResolver([{ name: 'menu', loadAll: () => new Map([['rotatingmachine', { softwareType: 'rotatingmachine', suppliers: [{ id: 'hidrostal', name: 'Hidrostal', types: [{ id: 'pump-centrifugal', name: 'Centrifugal', models: [{ id: 'm1', name: 'M-one', units: ['l/s', 'm3/h'] }], }], }], }]]), }]); const meta = r.resolveAssetMetadata('rotatingmachine', 'm1'); assert.equal(meta.supplier, 'Hidrostal'); assert.equal(meta.type, 'Centrifugal'); assert.equal(meta.model, 'M-one'); assert.deepEqual(meta.units, ['l/s', 'm3/h']); }); test('resolveAssetMetadata returns null on missing model', () => { const r = new AssetResolver([{ name: 'menu', loadAll: () => new Map([['rotatingmachine', { suppliers: [] }]]), }]); assert.equal(r.resolveAssetMetadata('rotatingmachine', 'm-nope'), null); assert.equal(r.resolveAssetMetadata('rotatingmachine', null), null); assert.equal(r.resolveAssetMetadata(null, 'm1'), null); });