import assert from 'node:assert/strict' import { createPipeModelBatches } from '../../../src/modules/cockpit/services/pipeModelBatches.js' const pending = [], removed = [], changes = [] const layer = createPipeModelBatches({ load: async (group, events) => { const model = { key: group.key }; pending.push({ group, events, model }); return model }, remove: (model) => removed.push(model.key), onChange: (state) => changes.push(state), }) const group = { key: 'tile:valve', instances: [{ id: 'component:v' }] } await layer.sync([group]) assert.deepEqual(layer.state().covered, [], 'await load is NOT model ready') pending[0].events.ready() assert.deepEqual(layer.state().covered, ['component:v']) await layer.sync([structuredClone(group)]) assert.equal(pending.length, 1, 'unchanged input must not reload model') pending[0].events.error(new Error('late buffer error')) assert.deepEqual(layer.state().covered, [], 'late error restores fallback') assert.deepEqual(removed, ['tile:valve']) assert.equal(layer.state().failed, 1) await layer.sync([]) assert.equal(layer.state().failed, 0) console.log('PASS readiness, immutable diff, late resource failure and clear') let finish const staleRemoved = [] const stale = createPipeModelBatches({ load: async (_, events) => { await new Promise((resolve) => { finish = resolve }) events.ready() return { id: 'stale' } }, remove: (model) => staleRemoved.push(model.id) }) const waiting = stale.sync([group]) await stale.clear() finish() await waiting assert.deepEqual(stale.state().covered, []) assert.deepEqual(staleRemoved, ['stale']) const splitEvents = [] const split = createPipeModelBatches({ load: async (g, e) => { splitEvents.push(e); return {} }, remove: () => {} }) await split.sync([{ key:'a', instances:[{id:'pipe:p'}] },{key:'b', instances:[{id:'pipe:p'}]}]) splitEvents[0].ready() assert.deepEqual(split.state().covered, [], 'do not hide fallback until ALL path spans are ready') splitEvents[1].ready() assert.deepEqual(split.state().covered, ['pipe:p']) console.log('PASS stale completions cannot resurrect, split path waits for all spans') let clock = 0 const attempts = [] const retry = createPipeModelBatches({ now: () => clock, retryMs: 100, load: async (_, events) => { attempts.push(events) return {} }, remove: () => {} }) await retry.sync([group]) attempts[0].error() await retry.sync([group]) assert.equal(attempts.length, 1, 'retry must respect cooldown') clock = 101 await retry.sync([group]) assert.equal(attempts.length, 2, 'same input recovers after temporary failure') attempts[1].ready() assert.deepEqual(retry.state().covered, ['component:v']) const stable = {key:'stable', instances:[{id:'component:s'}]} await retry.sync([group, stable]) assert.equal(attempts.length, 3, 'adding one batch leaves ready batch intact') console.log('PASS failed batch retries with cooldown, incremental additions retain ready batches')