Files
gstack/test/cso-rails-verification.test.ts
Garry Tan 5885157a90 v1.91.29.0 fix(cso): native runtime qualification passes for Node, Bun, Python and Rails (#3044)
* fix(cso): unblock native runtime qualification (v1.91.28.0)

Fixes the qualify-native failures from main run 37345275258:
- re-bind promotion evidence for staged runtimes in cold-start tests
- give npm a distinct empty global config (npm 11 refuses double-loading)
- export /archives and /work through tmpfs-backed local volumes (docker cp
  cannot read tmpfs), with exec on /work
- keep the offline seed process alive
- end broker/forwarder tunnels explicitly under Bun
- batch gem fetch per platform
- wait for run-app's source copy before Rails db:prepare and tests
- fix the Python cold fixture and add bin/rails to the Rails cold fixture

* fix(cso): bounded 900 s ceiling for preparation dependency commands

- PREPARATION_COMMAND_TIMEOUT_MS (900 s) applies only to acquisition and
  offline dependency commands; everything else keeps COMMAND_TIMEOUT_MS
  (300 s), and both are cut to the journey deadline
- prepared-tree export refusals fail with PREPARED_EXPORT_REJECTED and the
  helper's fixed reason or errno, never target paths, with no retry
- free tests pin the ceiling, its callers, and the rejection parser

* Take v1.91.29.0 (1.91.28.0 is claimed by #3032 and #3033)

* cso-diff-mode accepts every correct name for the unauthenticated webhook finding
2026-10-06 10:32:19 -07:00

60 lines
5.6 KiB
TypeScript

import { afterEach, describe, expect, spyOn, test } from 'bun:test';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { DockerGroup } from '../lib/cso/docker';
import { DockerVerificationExecutor } from '../lib/cso/verification';
const roots:string[]=[];
afterEach(()=>{for(const root of roots.splice(0))fs.rmSync(root,{recursive:true,force:true});});
describe('CSO Rails database verification lifecycle',()=>{
test('creates fresh multi-database PostgreSQL per phase and prepares every private work tree',async()=>{
const root=fs.mkdtempSync(path.join(os.tmpdir(),'cso-rails-verification-'));roots.push(root);
const source=path.join(root,'source'),work=path.join(root,'work'),control=path.join(root,'control');for(const dir of [source,work,control])fs.mkdirSync(dir);
const events:string[]=[],policies:string[][]=[];let groupNumber=0;
const create=spyOn(DockerGroup,'create').mockImplementation(async()=>{
const group=++groupNumber;let next=0;
return {
anchor:`anchor-${group}`,
createContainer:async(spec:any)=>{const id=`${group}-${spec.role}-${++next}`;events.push(`create:${id}:${spec.command.join(' ')}`);if(spec.postgresDatabasePolicy){policies.push(fs.readFileSync(spec.postgresDatabasePolicy,'utf8').trim().split('\n'));expect(fs.statSync(spec.postgresDatabasePolicy).mode&0o777).toBe(0o444);}return id;},
start:async(id:string)=>{events.push(`start:${id}`);},startHeldApplication:async(id:string)=>{events.push(`held:${id}`);},
execCapture:async(id:string,command:string[])=>{events.push(`exec:${id}:${command.join(' ')}`);return{code:0,stdout:'',stderr:''};},
execDetached:async(id:string,command:string[])=>{events.push(`detach:${id}:${command.join(' ')}`);},
startAttach:async(id:string)=>{events.push(`attach:${id}`);return{code:0,output:JSON.stringify({booted:true,legitimate:true,security:'pass',existingTests:false,output:'ok',inputHash:''})};},
removeContainer:async(id:string)=>{events.push(`remove:${id}`);},cleanup:async()=>{events.push(`cleanup:${group}`);},
} as any;
});
try{
const runtime:any={id:'rails',stack:'rails',image:`runtime@sha256:${'a'.repeat(64)}`,platform:'linux/amd64'};
const request:any={findingId:'b'.repeat(32),port:3456,start:{executable:'/usr/local/bin/bundle',args:['exec','rails','server']},
legitimate:[],security:{},fixtures:{},existingTests:[{executable:'/usr/local/bin/bundle',args:['exec','rspec']},{executable:'/usr/local/bin/bundle',args:['exec','rails','test']}]};
const executor=new DockerVerificationExecutor({} as any,'/watchdog',Date.now()+60_000);
const execution:any={environment:{BUNDLE_PATH:'/work/vendor/bundle'},database:{adapter:'postgresql',connections:['primary','queue'],sidecar:{id:'postgresql-17',image:`postgres@sha256:${'c'.repeat(64)}`}}};
await executor.observe(source,'before',request,runtime,runtime,work,control,execution);
await executor.observe(source,'after',request,runtime,runtime,work,control,execution);
}finally{create.mockRestore();}
expect(policies).toEqual([['cso_primary','cso_queue'],['cso_primary','cso_queue']]);
expect(events.filter(event=>event.includes('/opt/cso/postgresql-ready'))).toHaveLength(2);
expect(events.filter(event=>event.includes('/usr/local/bin/bundle exec rails db:prepare'))).toHaveLength(6);
for(const [index,event] of events.entries())if(event.includes('exec rails db:prepare'))expect(events.slice(0,index)).toContain(`held:${event.split(':')[1]}`);
expect(events.some(event=>/^start:\d-(app|tests)-/.test(event))).toBe(false);
for(const group of [1,2]){
const removeApp=events.findIndex(event=>event===`remove:${group}-app-2`),createFirstTest=events.findIndex(event=>event.startsWith(`create:${group}-tests-4:`));
expect(removeApp).toBeGreaterThan(-1);expect(createFirstTest).toBeGreaterThan(removeApp);
}
});
test('SQLite prepares the app and each test copy without a sidecar',async()=>{
const root=fs.mkdtempSync(path.join(os.tmpdir(),'cso-rails-sqlite-'));roots.push(root);for(const name of ['source','work','control'])fs.mkdirSync(path.join(root,name));
const commands:string[]=[];let next=0;
const create=spyOn(DockerGroup,'create').mockResolvedValue({
createContainer:async(spec:any)=>{commands.push(`create:${spec.role}`);return`${spec.role}-${++next}`;},start:async()=>{},startHeldApplication:async(id:string)=>{commands.push(`held:${id}`);},
execCapture:async(_id:string,command:string[])=>{commands.push(command.join(' '));return{code:0,stdout:'',stderr:''};},execDetached:async()=>{},
startAttach:async()=>({code:0,output:JSON.stringify({booted:true,legitimate:true,security:'pass',existingTests:false,output:'ok',inputHash:''})}),removeContainer:async()=>{},cleanup:async()=>{},
} as any);
try{const runtime:any={stack:'rails',image:`runtime@sha256:${'a'.repeat(64)}`},request:any={findingId:'d'.repeat(32),port:3456,start:{executable:'/usr/local/bin/bundle',args:['exec','rails','server']},legitimate:[],security:{},fixtures:{},existingTests:[{executable:'/usr/local/bin/bundle',args:['exec','rails','test']}]};await new DockerVerificationExecutor({} as any,'/watchdog',Date.now()+60_000).observe(path.join(root,'source'),'before',request,runtime,runtime,path.join(root,'work'),path.join(root,'control'),{environment:{},database:{adapter:'sqlite',connections:['primary']}});}finally{create.mockRestore();}
expect(commands.some(command=>command==='create:postgres')).toBe(false);expect(commands.filter(command=>command.includes('exec rails db:prepare'))).toHaveLength(2);expect(commands.filter(command=>command.startsWith('held:'))).toHaveLength(2);
});
});