Files
MikrotikManager/backend/scripts/acceptance/installation-memory-observation.test.mjs
T

32 lines
2.7 KiB
JavaScript

import assert from 'node:assert/strict'
import { test } from 'node:test'
import { evaluateObservedInstallationBudget as evaluate } from './installation-memory-budget.mjs'
const MiB=1024*1024
function fixture() {
const containers=['backend','postgres','frontend','portal'].map((role,index)=>({id:String(index+1).repeat(64),role,running:true,imageId:'sha256:'+'a'.repeat(64),startedAt:'2026-10-28T12:00:00Z',memoryLimitBytes:0,reportedLimitBytes:2*1024*MiB}))
const roleById=Object.fromEntries(containers.map(row=>[row.id,row.role]))
return {inventory:{readOnly:true,containerSetStable:true,environmentRead:false,serviceChanges:false,newFixtureStarted:false,hostMemTotalBytes:2021212*1024,containers},roleById}
}
test('observed zero configured caps stay unbounded despite positive Docker reported host limits',()=>{
const f=fixture(), result=evaluate(f.inventory,f.roleById)
assert.equal(result.budgetFits,false);assert(result.reasons.includes('UNBOUNDED_CONTAINER'));assert.equal(result.configuredContainerBytes,0)
assert.equal(result.wholeInstallation2GbAccepted,false);assert.equal(result.roleLabelsAuthenticated,false);assert.equal(result.inventoryFreshnessProven,false)
})
test('complete explicit configured caps can fit arithmetic without becoming runtime acceptance',()=>{
const f=fixture();[512,384,160,192].forEach((limit,index)=>{f.inventory.containers[index].memoryLimitBytes=limit*MiB})
const result=evaluate(f.inventory,f.roleById);assert(result.budgetFits);assert.equal(result.plannedBytes,1760*MiB);assert.equal(result.changesAuthorized,false)
})
test('uncertain snapshots, started fixtures and missing limit/identity fields refuse',()=>{
for(const mutate of [f=>{f.inventory.containerSetStable=false},f=>{f.inventory.serviceChanges=true},f=>{f.inventory.newFixtureStarted=true},f=>{delete f.inventory.containers[0].memoryLimitBytes},f=>{f.inventory.containers[0].running=false},f=>{f.inventory.containers[0].imageId='unknown'},f=>{f.inventory.containers[0].startedAt='invalid'}]){const f=fixture();mutate(f);assert.throws(()=>evaluate(f.inventory,f.roleById))}
})
test('missing, foreign, inherited and accessor role labels cannot silently classify a service',()=>{
for(const mode of ['missing','foreign','inherited','accessor']){
const f=fixture(),key=f.inventory.containers[0].id
if(mode==='missing')delete f.roleById[key]
if(mode==='foreign')f.roleById['f'.repeat(64)]='other'
if(mode==='inherited'){const labels=Object.create({[key]:'backend'});for(const [id,role]of Object.entries(f.roleById))if(id!==key)labels[id]=role;f.roleById=labels}
if(mode==='accessor')Object.defineProperty(f.roleById,key,{enumerable:true,get(){assert.fail('Role getter must not execute')}})
assert.throws(()=>evaluate(f.inventory,f.roleById))
}
})