224 lines
23 KiB
TypeScript
224 lines
23 KiB
TypeScript
// Candidate component tests. Actual signature/current reader, modeled PG/source/catalogue.
|
|
// No genuine SQL, TLS session, filesystem ACL or generation acceptance is claimed.
|
|
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import fs from 'node:fs/promises'
|
|
import {createRequire} from 'node:module'
|
|
import {runInNewContext} from 'node:vm'
|
|
import {EventEmitter} from 'node:events'
|
|
import {generateKeyPairSync} from 'node:crypto'
|
|
import path from 'node:path'
|
|
import * as format from '../src/db/application-writer-profile-format.ts'
|
|
import {MIGRATION_MANIFEST,migrationManifestDigest} from '../src/db/migration-manifest.ts'
|
|
import {readCurrentApplicationWriterProfile} from '../src/db/application-writer-profile-registry-read.ts'
|
|
import {syntheticFixture,stagedModelClient} from '../src/db/application-writer-profile-registry-read-model.test.ts'
|
|
import {approvedDefinition} from './writer-profile/catalogue.mts'
|
|
import {BUILD44_PROVENANCE,FIXED44_ROLE_TEMPLATE} from './writer-profile/fixed44-build-definition.ts'
|
|
const require=createRequire(new URL('../package.json',import.meta.url)),ts=require('typescript'),pg=require('pg')
|
|
const candidate=await fs.readFile(new URL('./writer-profile/publisher.mjs',import.meta.url),'utf8')
|
|
interface PreflightDiagnostics {state:string;headSha256:string;sourceMatched:boolean;rolesMatched:boolean;catalogueMatched:boolean;platformProtectionVerified:boolean;writerAuthorized:boolean;generationAuthenticated:boolean;readyAuthorized:boolean}
|
|
interface PublisherModule {
|
|
requireCurrentCatalogueDefinition(approval:unknown,definition:unknown):string
|
|
createConnectedPublisher(installation:unknown,collect:()=>Promise<unknown>,sourceVerifier:{verify:()=>Promise<{platformProtectionVerified:boolean}>},approval:unknown,definition:unknown):{
|
|
preflightCurrentProfile(pool:unknown,options:{deadlineAt:number}):Promise<PreflightDiagnostics>
|
|
publishCurrentProfile(pool:unknown,options:{privateKeyFile:string;expectedHeadSha256:null;journalDirectory:string;deadlineAt:number}):Promise<{outcome:string}>
|
|
}
|
|
}
|
|
interface OperatorModule {main(args:string[]):Promise<{event:string;outcome:string;platformProtectionVerified:boolean;writerAuthorized:boolean;readyAuthorized:boolean;sourceActivationMatched?:boolean}>}
|
|
function compile<T>(source:string,modules:Record<string,unknown>):T{
|
|
const exports:Record<string,unknown>={};const compiled=ts.transpileModule(source.split('\nif(process.argv[1]')[0].replaceAll('import.meta.url',JSON.stringify('file:///candidate-not-cli.mts')),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
|
|
runInNewContext(compiled,{exports,require:(name:string)=>{assert(name in modules,'Unapproved import '+name);return modules[name]},Buffer,URL,TextDecoder,Date,setTimeout,clearTimeout,Object,Error,AggregateError,Promise,Map,Set,process:{env:{},argv:[]}}, {filename:'candidate.mjs'})
|
|
// The isolated VM loads the exact source above; tests exercise this narrow export contract.
|
|
return exports as T
|
|
}
|
|
const frozenDefinition=approvedDefinition({format:'application-writer-source44-definition-approval-v1',provenance:BUILD44_PROVENANCE,template:FIXED44_ROLE_TEMPLATE})
|
|
const frozenMigrations=MIGRATION_MANIFEST.slice(0,44),markerIds=frozenMigrations.map(pin=>pin.file.slice(0,-4))
|
|
const markerSql='SELECT id FROM public.schema_migrations ORDER BY id COLLATE "C" LIMIT $1'
|
|
function fixture(fault='',forPublication=false){
|
|
const f=syntheticFixture('1'),catalogue={...frozenDefinition,catalogSha256:'f'.repeat(64),leafCount:117},security={sha256:'e'.repeat(64),roles:{...f.policy.roles}}
|
|
const currentBuild=fault==='current-build'||fault==='current-approved-frozen-definition'
|
|
f.policy.manifestSha256=frozenDefinition.manifestSha256;f.policy.definitionSha256=frozenDefinition.definitionSha256
|
|
f.policy.catalogSha256=format.sha(format.canonical({base:catalogue.catalogSha256,security:security.sha256}))
|
|
if(fault==='signed-source')f.policy.source.databaseOid='999'
|
|
if(fault==='signed-role')f.policy.roles.operator='99'
|
|
if(fault==='signed-catalogue')f.policy.catalogSha256='1'.repeat(64)
|
|
if(fault==='signed-definition')f.policy.definitionSha256='1'.repeat(64)
|
|
if(fault==='signed-manifest')f.policy.manifestSha256='1'.repeat(64)
|
|
if(fault==='expired'){f.policy.issuedAt=Date.now()-60000;f.policy.notBefore=f.policy.issuedAt;f.policy.notAfter=Date.now()-1000}
|
|
const raw=f.signBody(f.policy,format.POLICY_DOMAIN),hash=format.sha(raw),top=f.state.latest[0]
|
|
top.policy_sha256=hash;top.head_bytes=f.signBody({format:'application-writer-head-v1',installationId:f.subject.installationId,epoch:'1',policySha256:hash,previousHeadSha256:null},format.HEAD_DOMAIN);top.head_sha256=format.sha(top.head_bytes)
|
|
f.state.current[0].head_sha256=top.head_sha256;f.state.policy=[{policy_sha256:hash,policy_bytes:raw}]
|
|
if(fault==='tamper')f.state.policy[0].policy_bytes=Buffer.from(raw.toString().replace('synthetic','tampered'))
|
|
if(fault==='empty'){f.state.current=[];f.state.latest=[];f.state.policy=[]}
|
|
const model=stagedModelClient(f.state),queries:string[]=[],events:string[]=[],markerLimits:unknown[][]=[]
|
|
let clocks=0,checkouts=0
|
|
const client=Object.assign(new EventEmitter(),{query:async(sql:string,values?:unknown[])=>{
|
|
queries.push(sql)
|
|
if(sql.includes('clock_timestamp()')&&++clocks===2&&fault==='late-clock')return {rows:[{now:'1'}]}
|
|
if(sql===markerSql){
|
|
markerLimits.push(values??[])
|
|
const ids=fault==='marker-missing'?markerIds.slice(0,-1):fault==='marker-extra'||fault==='marker-drift'&&markerLimits.length===2?[...markerIds,'9999_foreign']:markerIds
|
|
return {rows:ids.map(id=>({id}))}
|
|
}
|
|
if(sql.startsWith('WITH')||sql.includes('clock_timestamp()'))return model.client.query(sql,values)
|
|
if(sql.startsWith('SELECT pg_catalog.pg_advisory_unlock'))return {rows:[{released:true}]}
|
|
if(sql==='ROLLBACK')return {rows:[],command:fault==='rollback-ack'?'':'ROLLBACK'}
|
|
if(sql.startsWith('INSERT INTO mmapp_control.current'))return {rows:[{id:1}]}
|
|
return {rows:[],command:sql.split(' ')[0]}
|
|
},release:(bad:boolean)=>{events.push('release:'+bad)}})
|
|
const pool=new pg.Pool();pool.connect=async()=>{checkouts++;return client}
|
|
const source={systemIdentifier:'123',databaseOid:'456',databaseName:'synthetic',serverVersionNum:180004,inRecovery:false}
|
|
let contexts=0,verifications=0
|
|
const fixtureKey=forPublication?generateKeyPairSync('ed25519'):null
|
|
const subject=fixtureKey?{...f.subject,publicKeyPem:fixtureKey.publicKey.export({format:'pem',type:'spki'}).toString(),publicKeySpkiSha256:format.sha(fixtureKey.publicKey.export({format:'der',type:'spki'}))}:f.subject
|
|
const installation={...subject,expectedSource:source,roleBindings:{publisher:{name:'publisher',signedOid:'14'}}}
|
|
const approval={expiresAt:Date.now()+60000,manifestSha256:['approval-manifest','current-approved-frozen-definition'].includes(fault)?migrationManifestDigest():frozenDefinition.manifestSha256,definitionSha256:catalogue.definitionSha256,catalogSha256:format.sha(format.canonical({base:catalogue.catalogSha256,security:security.sha256}))}
|
|
const definition=fault==='static-definition'?{...frozenDefinition,definitionSha256:'0'.repeat(64)}:frozenDefinition
|
|
const privateKeyFile=path.resolve('modeled-fixture-key.pem'),journalDirectory=path.resolve('modeled-journal'),files=new Map<string,Buffer>()
|
|
if(fixtureKey)files.set(privateKeyFile,Buffer.from(fixtureKey.privateKey.export({format:'pem',type:'pkcs8'})))
|
|
function file(filename:string){const value=files.get(filename);if(!value)throw Object.assign(Error('Fixture file absent'),{code:'ENOENT'});return value}
|
|
function stat(filename:string){return {size:file(filename).length,dev:1,ino:1,isFile:()=>true,isSymbolicLink:()=>false}}
|
|
const modules={
|
|
pg,'node:crypto':require('node:crypto'),'node:fs/promises':{realpath:async(file:string)=>file,lstat:async(filename:string)=>stat(filename),unlink:async(filename:string)=>files.delete(filename),open:async(filename:string)=>({stat:async()=>stat(filename),readFile:async()=>Buffer.from(file(filename)),close:async()=>{}})},'node:path':path,
|
|
'./operator-contract.mjs':{retainPublisherTlsPeer:(actual:unknown)=>{
|
|
assert.equal(actual,client)
|
|
if(fault==='initial-tls')throw Error('TLS peer refused')
|
|
return ()=>{if(fault==='lost-tls'&&verifications===2)throw Error('Retained TLS lost')}
|
|
}},
|
|
'./operator-files.mjs':{withJournalLease:async(_directory:string,work:()=>Promise<unknown>)=>{assert(forPublication);events.push('journal-lease');return work()},writeFile:async(filename:string,value:Buffer)=>{assert(forPublication);files.set(filename,value)}},
|
|
'../../src/db/database-source-observation.js':{captureDatabaseSourceObservation:async()=>{events.push('capture-source');return {}},assertDatabaseSourceObservationCurrent:async()=>{events.push('fresh-source');if(fault==='late-source')throw Error('Source changed')}},
|
|
'../../src/db/migration-manifest.js':{MIGRATION_MANIFEST:currentBuild?MIGRATION_MANIFEST:frozenMigrations,migrationManifestDigest:()=>currentBuild?migrationManifestDigest():frozenDefinition.manifestSha256},
|
|
'../../src/db/application-writer-profile-format.js':format,
|
|
'./context.mjs':{ownedContext:async()=>{contexts++;if(fault==='late-actor'&&contexts===2)throw Error('Actor changed');return {pid:1,source}},sameOwned:(a:unknown,b:unknown)=>assert.deepEqual(a,b),sourceCoordinates:(v:{source:typeof source})=>v.source,protectedStructure:async()=>{events.push('roles');if(fault==='role-structure')throw Error('Unsafe role');return security},readCurrent:forPublication?async()=>null:readCurrentApplicationWriterProfile,HISTORY:'history',CURRENT:'current'},
|
|
}
|
|
const api=compile<PublisherModule>(candidate,modules).createConnectedPublisher(installation,async()=>{events.push('catalogue');if(fault==='catalogue-eof')throw Error('Incomplete EOF');return fault==='catalogue-manifest'?{...catalogue,manifestSha256:migrationManifestDigest()}:fault==='catalogue-definition'?{...catalogue,definitionSha256:'0'.repeat(64)}:catalogue},{verify:async()=>{verifications++;events.push('verify-source');if(fault==='source-verifier')throw Error('Unsigned source');if(fault==='late-expiry'&&verifications===2)approval.expiresAt=1;return {platformProtectionVerified:!(fault==='initial-unprotected'&&verifications===1||fault==='terminal-unprotected'&&verifications===2)}}},approval,definition)
|
|
return {run:()=>api.preflightCurrentProfile(pool,{deadlineAt:Date.now()+30000}),publish:()=>api.publishCurrentProfile(pool,{privateKeyFile,journalDirectory,expectedHeadSha256:null,deadlineAt:Date.now()+30000}),queries,events,markerLimits,files,get checkouts(){return checkouts},f}
|
|
}
|
|
test('current44 preflight authenticates signed current and actual component inputs on one read-only lease',async()=>{
|
|
const f=fixture(),result=await f.run();assert.equal(result.state,'matched');assert.equal(result.headSha256,f.f.state.current[0].head_sha256)
|
|
assert.equal(result.sourceMatched,true);assert.equal(result.rolesMatched,true);assert.equal(result.catalogueMatched,true)
|
|
assert.equal(result.platformProtectionVerified,true)
|
|
assert.equal(result.writerAuthorized,false);assert.equal(result.generationAuthenticated,false);assert.equal(result.readyAuthorized,false)
|
|
assert.equal(f.queries.filter(q=>q.startsWith('BEGIN')).length,1);assert(f.queries.includes('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY'));assert.equal(f.queries.filter(q=>q==='ROLLBACK').length,1)
|
|
assert.equal(format.canonical(f.markerLimits),'[[45]]')
|
|
assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT|CREATE|ALTER|DROP)\b/.test(q)));assert.equal(f.events.at(-1),'release:false')
|
|
assert.deepEqual(f.events.slice(0,6),['capture-source','verify-source','roles','catalogue','verify-source','fresh-source'])
|
|
})
|
|
test('publisher refuses before SQL on a wrong peer and stops SQL when the retained TLS socket is lost',async()=>{
|
|
const initial=fixture('initial-tls')
|
|
await assert.rejects(initial.run());assert.deepEqual(initial.queries,[]);assert.deepEqual(initial.events,['release:true'])
|
|
const lost=fixture('lost-tls')
|
|
await assert.rejects(lost.run());assert.equal(lost.events.at(-1),'release:true')
|
|
assert(!lost.queries.some(q=>q==='ROLLBACK'||q.includes('pg_advisory_unlock')))
|
|
})
|
|
for(const fault of ['initial-unprotected','terminal-unprotected'])test('preflight cannot promote '+fault+' into protected evidence',async()=>{
|
|
const f=fixture(fault),result=await f.run();assert.equal(result.platformProtectionVerified,false)
|
|
assert.equal(result.writerAuthorized,false);assert.equal(result.readyAuthorized,false)
|
|
assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT|CREATE|ALTER|DROP)\b/.test(q)))
|
|
})
|
|
for(const fault of ['tamper','empty','expired','signed-source','signed-role','signed-catalogue','signed-definition','signed-manifest','role-structure','catalogue-eof','source-verifier','late-source','late-actor','late-expiry','late-clock','rollback-ack'])test('preflight refuses '+fault+' without any write or replay',async()=>{
|
|
const f=fixture(fault);await assert.rejects(f.run());assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT|CREATE|ALTER|DROP)\b/.test(q)));assert.equal(f.queries.filter(q=>q.startsWith('BEGIN')).length,1)
|
|
})
|
|
for(const fault of ['current-build','current-approved-frozen-definition','approval-manifest','static-definition'])test('incompatible static manifest/definition refuses '+fault+' before either pool checkout or publication lease',async()=>{
|
|
const f=fixture(fault,true)
|
|
await assert.rejects(f.run(),/Approved catalogue definition is not bound/)
|
|
await assert.rejects(f.publish(),/Approved catalogue definition is not bound/)
|
|
assert.equal(f.checkouts,0);assert.deepEqual(f.events,[]);assert.deepEqual(f.queries,[])
|
|
})
|
|
for(const fault of ['catalogue-manifest','catalogue-definition','marker-missing','marker-extra'])test('retained preflight refuses '+fault+' and rolls back',async()=>{
|
|
const f=fixture(fault)
|
|
await assert.rejects(f.run(),error=>error instanceof Error&&/Catalogue evidence differs|Exact current migration marker/.test((error.cause as Error).message))
|
|
assert.equal(f.checkouts,1);assert.equal(f.queries.filter(q=>q==='ROLLBACK').length,1)
|
|
assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT)\b/.test(q)))
|
|
if(fault.startsWith('marker-'))assert.equal(format.canonical(f.markerLimits),'[[45]]')
|
|
else assert.deepEqual(f.markerLimits,[])
|
|
})
|
|
test('publication checks exact marker set on both retained transactions and refuses late marker drift before registry writes',async()=>{
|
|
const positive=fixture('',true)
|
|
assert.equal((await positive.publish()).outcome,'committed')
|
|
assert.equal(positive.checkouts,1);assert.equal(format.canonical(positive.markerLimits),'[[45],[45]]')
|
|
assert.equal(positive.queries.filter(q=>q==='COMMIT').length,2)
|
|
const drift=fixture('marker-drift',true)
|
|
await assert.rejects(drift.publish(),error=>error instanceof Error&&/Exact current migration marker/.test((error.cause as Error).message))
|
|
assert.equal(drift.checkouts,1);assert.equal(format.canonical(drift.markerLimits),'[[45],[45]]')
|
|
assert.equal(drift.queries.filter(q=>q==='COMMIT').length,1);assert.equal(drift.queries.filter(q=>q==='ROLLBACK').length,1)
|
|
assert(!drift.queries.some(q=>/^(?:INSERT|UPDATE|DELETE)\b/.test(q)))
|
|
assert([...drift.files.keys()].some(filename=>filename.endsWith('pending-publication.json')))
|
|
})
|
|
test('operator requires the protected source verifier for preflight, publication and reconciliation',async()=>{
|
|
const cli=await fs.readFile(new URL('./database-writer-profile-operator.mts',import.meta.url),'utf8'),f=syntheticFixture('1'),reads:string[]=[],events:string[]=[]
|
|
const request={installationId:f.subject.installationId,roles:Object.fromEntries(['runtime','operator','capture','publisher','control'].map((k,i)=>[k,{name:k,signedOid:String(i+11)}])),source:f.policy.source,sourceHeadSha256:'a'.repeat(64),tls:{caSha256:'a'.repeat(64),spkiSha256:'d'.repeat(64)}}
|
|
const files:Record<string,unknown>={bootstrap:{format:'application-writer-bootstrap-v1',publicationAuthorized:false,readyAuthorized:false,deploymentRequest:request,verificationSubject:f.subject},approval:{expiresAt:Date.now()+60000,definitionSha256:'b'.repeat(64)},definition:{},transport:{},ca:'fixture','signing-private.pem':'fixture'}
|
|
let failEnd=false,asyncError=false,protectedRefused=false,activationRefused=false,incompatibleDefinition=false
|
|
const retainedPools:Pool[]=[]
|
|
class Pool extends EventEmitter{constructor(){super();retainedPools.push(this)}async end(){events.push('pool-end');if(failEnd)throw Error('Pool cleanup unconfirmed')}}
|
|
const result={state:'matched',epoch:'1',headSha256:'c'.repeat(64),notAfter:Date.now()+60000,platformProtectionVerified:true}
|
|
const protectedVerifier={verify:async()=>({platformProtectionVerified:true})}
|
|
const guardedVerifier={...protectedVerifier}
|
|
const modules={
|
|
'node:assert/strict':assert,'node:path':require('node:path'),'node:url':require('node:url'),'node:crypto':require('node:crypto'),
|
|
'./writer-profile/operator-files.mjs':{readProtected:async(file:string)=>{reads.push(file);assert(file in files);return Buffer.from(JSON.stringify(files[file]))},protectedDirectory:async()=>{},sha:format.sha},
|
|
'./writer-profile/operator-contract.mjs':{approval:(value:unknown)=>{events.push('approval');return value},reconciliationApproval:(value:unknown)=>{events.push('reconciliation-approval');return value},poolOptions:()=>{events.push('transport');return {}},roleKeys:['runtime','operator','capture','publisher','control'],assertJsDriverEnvironment:()=>{}},
|
|
'./database-writer-bootstrap-operator.mjs':{deploymentRequest:(value:unknown)=>value},
|
|
'./writer-profile/protected-source-registry.mjs':{createProtectedDatabaseSourceBindingVerifier:(pin:{registryDirectory:string;currentHeadSha256:string})=>{events.push('protected-source');assert.equal(pin.registryDirectory,'source-registry');assert.equal(pin.currentHeadSha256,request.sourceHeadSha256);if(protectedRefused)throw Error('Protected source refused');return protectedVerifier}},
|
|
'./writer-profile/source-activation.mjs':{withActiveSourcePreflight:async(input:{directory:string;expectedSha256:string;sourcePin:{currentHeadSha256:string};expectedSource:unknown;tlsSpkiSha256:string;deadlineAt:number},verifier:unknown,work:(verifier:unknown)=>Promise<unknown>)=>{
|
|
assert.equal(verifier,protectedVerifier);assert.equal(input.directory,'active-registry');assert.equal(input.expectedSha256,'e'.repeat(64))
|
|
assert.equal(input.sourcePin.currentHeadSha256,request.sourceHeadSha256);assert.equal(input.tlsSpkiSha256,request.tls.spkiSha256);assert(input.deadlineAt>Date.now())
|
|
assert.equal(format.canonical(input.expectedSource),format.canonical({...request.source,databaseOid:Number(request.source.databaseOid),sessionUser:'publisher',currentUser:'publisher'}))
|
|
events.push('activation-admission');if(activationRefused)throw Error('Activation refused')
|
|
const value=await work(guardedVerifier);events.push('activation-terminal');return value
|
|
}},
|
|
pg:{Pool},'./writer-profile/publisher.mjs':{requireCurrentCatalogueDefinition:()=>{if(incompatibleDefinition)throw Error('Build manifest refused')},createConnectedPublisher:(_installation:unknown,_collect:unknown,verifier:unknown)=>{
|
|
assert.equal(verifier,events.includes('activation-admission')?guardedVerifier:protectedVerifier)
|
|
return {
|
|
preflightCurrentProfile:async()=>{events.push('preflight');if(asyncError){const activePool=retainedPools.at(-1);assert(activePool);activePool.emit('error',Error('Async pool fault'))}return result},
|
|
publishCurrentProfile:async()=>{events.push('publish');return {...result,outcome:'committed'}},
|
|
reconcilePendingPublication:async()=>{events.push('reconcile');return {state:'confirmed',headSha256:result.headSha256}},
|
|
}
|
|
}},
|
|
'./writer-profile/catalogue.mjs':{collectSource44Catalogue:()=>{},approvedDefinition:()=>({definitionSha256:'b'.repeat(64),manifestSha256:frozenDefinition.manifestSha256})},
|
|
}
|
|
const flags=['bootstrap','bootstrap-sha256','approval','approval-sha256','definition','definition-sha256','transport','transport-sha256','ca','source-registry','source-spki-sha256','journal'],args=['preflight',...flags.flatMap(flag=>['--'+flag,flag.endsWith('sha256')?'a'.repeat(64):flag])]
|
|
const output=await compile<OperatorModule>(cli,modules).main(args)
|
|
assert.equal(output.event,'application-writer-profile-preflight');assert.equal(output.outcome,'matched');assert.equal(output.platformProtectionVerified,true);assert.equal(output.writerAuthorized,false);assert.equal(output.readyAuthorized,false)
|
|
assert.deepEqual(reads,['bootstrap','approval','definition','transport','ca']);assert.deepEqual(events,['approval','transport','protected-source','preflight','pool-end'])
|
|
failEnd=true;await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Pool cleanup unconfirmed/)
|
|
failEnd=false;asyncError=true;await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Publisher terminal unconfirmed/)
|
|
asyncError=false;result.platformProtectionVerified=false;await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Publisher terminal unconfirmed/)
|
|
result.platformProtectionVerified=true;protectedRefused=true;const before=retainedPools.length
|
|
await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Protected source refused/);assert.equal(retainedPools.length,before,'Protection failure must not open a Pool or fall back')
|
|
for(const [command,approvalEvent,outcome] of [['publish','approval','committed'],['reconcile','reconciliation-approval','confirmed']]){
|
|
const commandArgs=[command,...args.slice(1)]
|
|
const poolCount=retainedPools.length
|
|
await assert.rejects(compile<OperatorModule>(cli,modules).main(commandArgs),/Protected source refused/)
|
|
assert.equal(retainedPools.length,poolCount)
|
|
protectedRefused=false
|
|
events.length=0;reads.length=0
|
|
const terminal=await compile<OperatorModule>(cli,modules).main(commandArgs)
|
|
assert.equal(terminal.outcome,outcome);assert.equal(terminal.readyAuthorized,false)
|
|
assert.deepEqual(events,[approvalEvent,'transport','protected-source',command,'pool-end'])
|
|
assert.equal(reads.includes('signing-private.pem'),command==='publish')
|
|
protectedRefused=true
|
|
}
|
|
const activeArgs=['source-preflight',...args.slice(1),'--activation-directory','active-registry','--activation-sha256','e'.repeat(64)]
|
|
protectedRefused=false;activationRefused=true;events.length=0;reads.length=0
|
|
const beforeActivation=retainedPools.length
|
|
await assert.rejects(compile<OperatorModule>(cli,modules).main(activeArgs),/Activation refused/)
|
|
assert.equal(retainedPools.length,beforeActivation)
|
|
assert.deepEqual(events,['approval','transport','protected-source','activation-admission'])
|
|
activationRefused=false;events.length=0;reads.length=0
|
|
const active=await compile<OperatorModule>(cli,modules).main(activeArgs)
|
|
assert.equal(active.event,'application-writer-profile-source-preflight');assert.equal(active.sourceActivationMatched,true)
|
|
assert.equal(active.readyAuthorized,false);assert.equal(active.writerAuthorized,false)
|
|
assert.deepEqual(events,['approval','transport','protected-source','activation-admission','preflight','pool-end','activation-terminal'])
|
|
assert.deepEqual(reads,['bootstrap','approval','definition','transport','ca'])
|
|
incompatibleDefinition=true;events.length=0;reads.length=0
|
|
const beforeBuildRefusal=retainedPools.length
|
|
for(const incompatibleArgs of [args,activeArgs,['publish',...args.slice(1)]])await assert.rejects(compile<OperatorModule>(cli,modules).main(incompatibleArgs),/Build manifest refused/)
|
|
assert.equal(retainedPools.length,beforeBuildRefusal)
|
|
assert(!events.some(event=>event==='protected-source'||event==='activation-admission'))
|
|
const historical=await compile<OperatorModule>(cli,modules).main(['reconcile',...args.slice(1)])
|
|
assert.equal(historical.outcome,'confirmed');assert.equal(historical.readyAuthorized,false)
|
|
})
|
|
|