Files
gstack/test/autoplan-edit-queue-am.test.ts
Garry Tan 6b6e68f7af test: fix Windows CI fixtures and credential scan
Rebase captured JSON values and filesystem evidence using the appropriate
path convention. Compile native fake CLIs on Windows and synchronize pipe
holder readiness, with cleanup retained when assertions fail.

Assemble synthetic credential fixtures at runtime so the added-line scan
keeps enforcing the same gate without flagging its own rejection controls.

Discover generated skills directly for the empty-find regression check,
avoiding a recursive scan through saved evaluation artifacts and dependencies.
2026-09-11 17:37:16 +00:00

190 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { capturedPathRebaser } from './helpers/captured-paths';
import {expect,test} from 'bun:test';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import fixture from './fixtures/autoplan-edit-queue-am.json';
import * as permission from './helpers/autoplan-artifact-permission';
import {readPendingAutoplanArtifact} from './helpers/autoplan-artifact-recorder';
import {readPlanCountTranscript,type NativePublicToolEvent} from './helpers/plan-count-transcript';
import {E2E_TOUCHFILES,selectTests} from './helpers/touchfiles';
function setup(changeRecords?:(records:any[])=>void){
const dir=fs.mkdtempSync(path.join(os.tmpdir(),'gstack-ap-queue-'));
const cwd=path.join(dir,path.basename(fixture.cwd)),config=path.join(dir,'config');
const stateRoot=path.join(dir,'gstack-hermetic-2101964-HvDZyN/skill-home-zgCNxG/.gstack');
const rebase=capturedPathRebaser([[fixture.stateRoot,stateRoot],[fixture.cwd,cwd],[fixture.config,config]]);
const hook=rebase.json(fixture.hook);
const file=hook.pending.file,nativeFile=path.join(config,'projects','owned',hook.sessionId+'.jsonl');hook.pending.transcriptPath=nativeFile;
fs.mkdirSync(cwd,{recursive:true});fs.mkdirSync(path.dirname(file),{recursive:true});fs.mkdirSync(path.dirname(nativeFile),{recursive:true});
fs.writeFileSync(file,fixture.before);fs.utimesSync(file,new Date(fixture.now-1000000),new Date(Date.parse(hook.pending.timestamp)-1000));
const events=rebase.json(fixture.publicTools) as (NativePublicToolEvent & {messageId?:string;requestId?:string})[];
const records=events.map(e=>({sessionId:e.sessionId,cwd,isSidechain:false,timestamp:e.timestamp,requestId:e.requestId,message:{id:e.messageId,role:e.kind==='use'?'assistant':'user',content:e.kind==='use'?[{type:'tool_use',id:e.toolUseId,name:e.name,input:e.input}]:[{type:'tool_result',tool_use_id:e.toolUseId,content:'',is_error:e.isError}]}}));
changeRecords?.(records);
fs.writeFileSync(nativeFile,records.map(r=>JSON.stringify(r)).join('\n')+'\n');
const hookFile=path.join(dir,'hook.json');fs.writeFileSync(hookFile,JSON.stringify(hook)+'\n');
const publicTools:NativePublicToolEvent[]=[];const native=readPlanCountTranscript(config,cwd,e=>publicTools.push(e));
const pending=(readPendingAutoplanArtifact as any)(hookFile,cwd,config,stateRoot,fixture.commandStartedAt,publicTools,fixture.now,true);
const context={cwd,ownedStateRoot:stateRoot,commandStartedAt:fixture.commandStartedAt,now:fixture.now,viewportCapturedAt:fixture.now,transcriptStatus:native.status,publicTools,pending};
const invoke=(screen=fixture.viewport,ctx:any=context,seen=new Set<string>())=>(permission as any).publishedAutoplanArtifactPermissionInput?.(screen,ctx,seen)??null;
return {dir,cwd,config,stateRoot,hook,hookFile,file,nativeFile,publicTools,context,invoke,dispose:()=>fs.rmSync(dir,{recursive:true,force:true})};
}
test('the actual active hook binds its published request amid later queued edits and both native prefix forms',()=>{
const s=setup();try{
expect(permission.autoplanArtifactPermissionInput(fixture.viewport,s.context,new Set())).toBeNull();
expect(permission.pendingAutoplanArtifactPermissionInput(fixture.viewport,s.context,new Set())).toBeNull();
expect(s.context.pending?.toolUseId).toBe(fixture.hook.pending.toolUseId);
expect(s.invoke()?.signature).toBe(`${fixture.hook.sessionId}:${fixture.hook.pending.toolUseId}`);
expect(s.invoke()?.file).toBe(s.file);
expect(s.invoke()?.input).toBe('1\r');
}finally{s.dispose()}
});
test('the default metadata-only reader continues excluding a published request',()=>{
const s=setup();try{expect(readPendingAutoplanArtifact(s.hookFile,s.cwd,s.config,s.stateRoot,fixture.commandStartedAt,s.publicTools,fixture.now)).toBeUndefined();}finally{s.dispose()}
});
type Replay=ReturnType<typeof setup>;
const current=(s:Replay)=>s.context.publicTools.find(e=>e.kind==='use'&&e.toolUseId===fixture.hook.pending.toolUseId)!;
const queued=(s:Replay)=>s.context.publicTools.find(e=>e.kind==='use'&&e.toolUseId==='toolu_01SYiANcdq3hLqGxEhDQVNJf')!;
function rejects(cases:Array<[string,(s:Replay)=>void]>){
for(const [name,change] of cases){const s=setup();try{change(s);expect(s.invoke(),name).toBeNull()}finally{s.dispose()}}
}
test('only exact native message and request identifiers establish queued membership',()=>{
const s=setup();try{
expect(current(s).messageId).toBe('msg_011CeuYDnRH9L1Qoom8gBVdc');
expect(current(s).requestId).toBe('req_011CeuYDk9cAd6Yozh8QnH62');
expect(queued(s).messageId).toBe(current(s).messageId);
}finally{s.dispose()}
for(const change of [
(r:any)=>{delete r.message.id},(r:any)=>{delete r.requestId},
(r:any)=>{r.message.id='quoted msg_example'},(r:any)=>{r.requestId='req_'+ 'a'.repeat(161)},
]){const s=setup(records=>{for(const r of records)if(r.message.content[0]?.id===fixture.hook.pending.toolUseId)change(r)});try{
expect(current(s).messageId).toBeUndefined();expect(current(s).requestId).toBeUndefined();expect(s.invoke()).toBeNull();
}finally{s.dispose()}}
});
test.each(['one native record','equal timestamps'])('ordered later blocks in %s remain queued behind the current hook',shape=>{
const ids=['toolu_01SYiANcdq3hLqGxEhDQVNJf','toolu_01LgaibBToDfuxGNFBKew9PS','toolu_01VqJFXfD5cfdjiar1gAjpkV'];
const s=setup(records=>{
const active=records.find(r=>r.message.content[0]?.id===fixture.hook.pending.toolUseId)!;
for(let i=records.length-1;i>=0;i--){const r=records[i];if(!ids.includes(r.message.content[0]?.id))continue;
if(shape==='one native record'){active.message.content.splice(1,0,r.message.content[0]);records.splice(i,1)}
else r.timestamp=active.timestamp;
}
});try{
const active=current(s),remaining=s.publicTools.filter(e=>e.kind==='use'&&ids.includes(e.toolUseId));
expect(remaining.map(e=>e.toolUseId)).toEqual(ids);
expect(remaining.every(e=>e.timestamp===active.timestamp&&e.messageId===active.messageId&&e.requestId===active.requestId)).toBe(true);
expect(s.invoke()?.signature).toBe(s.hook.sessionId+':'+s.hook.pending.toolUseId);
// Moving a same-time unresolved block ahead of the current request is not a queued successor.
const earlier=remaining[0]!,events=s.context.publicTools;events.splice(events.indexOf(earlier),1);events.splice(events.indexOf(active),0,earlier);
expect(s.invoke()).toBeNull();
}finally{s.dispose()}
});
test('another batch, session, path, tool, malformed edit or already hooked successor cannot be ignored',()=>{
rejects([
['foreign message',s=>{queued(s).messageId='msg_other'}],
['foreign request',s=>{queued(s).requestId='req_other'}],
['missing message',s=>{delete queued(s).messageId}],
['foreign session',s=>{queued(s).sessionId='foreign'}],
['foreign file',s=>{queued(s).input!.file_path=s.file+'.other'}],
['queued Write',s=>{queued(s).name='Write'}],
['empty old request',s=>{queued(s).input!.old_string=''}],
['missing replacement',s=>{delete queued(s).input!.new_string}],
['replace all',s=>{queued(s).input!.replace_all=true}],
['already hooked',s=>{s.context.pending!.hookSeenIds!.push(queued(s).toolUseId)}],
['older unresolved',s=>{s.context.publicTools=s.context.publicTools.filter(e=>!(e.kind==='result'&&e.toolUseId==='toolu_01BbKwZ7JFFdm2FLFdcNQXPq'))}],
]);
});
test('current hook identity, completed or failed requests and ordering cannot be overridden',()=>{
rejects([
['foreign pending',s=>{s.context.pending!.sessionId='foreign'}],
['wrong current hook',s=>{s.context.pending!.toolUseId=queued(s).toolUseId}],
['missing hook',s=>{s.context.pending=undefined}],
['missing tombstones',s=>{delete s.context.pending!.hookSeenIds}],
['duplicate tombstone',s=>{s.context.pending!.hookSeenIds!.push(fixture.hook.pending.toolUseId)}],
['unseen current',s=>{s.context.pending!.hookSeenIds=[]}],
['duplicate current',s=>{const at=s.context.publicTools.indexOf(current(s));s.context.publicTools.splice(at,0,structuredClone(current(s)))}],
['completion',s=>{s.context.publicTools.push({kind:'result',sessionId:s.hook.sessionId,toolUseId:current(s).toolUseId,timestamp:s.hook.pending.timestamp,isError:false})}],
['failure',s=>{s.context.publicTools.push({kind:'result',sessionId:s.hook.sessionId,toolUseId:current(s).toolUseId,timestamp:s.hook.pending.timestamp,isError:true})}],
['completed queued',s=>{s.context.publicTools.push({kind:'result',sessionId:s.hook.sessionId,toolUseId:queued(s).toolUseId,timestamp:s.hook.pending.timestamp,isError:false})}],
['failed queued',s=>{s.context.publicTools.push({kind:'result',sessionId:s.hook.sessionId,toolUseId:queued(s).toolUseId,timestamp:s.hook.pending.timestamp,isError:true})}],
['late predecessor completion',s=>{s.context.publicTools.at(-1)!.timestamp=new Date(Date.parse(s.hook.pending.timestamp)+1).toISOString()}],
['no successful predecessor',s=>{for(const e of s.context.publicTools)if(e.kind==='result')e.isError=true}],
['out of order',s=>{s.context.publicTools.reverse()}],
['future publication',s=>{queued(s).timestamp=new Date(fixture.now+1).toISOString()}],
]);
});
test('exact digest and current before file are required independently of the visible subset',()=>{
rejects([
['missing digest',s=>{delete s.context.pending!.editDigest}],
['malformed digest',s=>{s.context.pending!.editDigest.version=2}],
['different request hash',s=>{s.context.pending!.editDigest.requestSHA256='0'.repeat(64)}],
['different before hash',s=>{s.context.pending!.editDigest.beforeSHA256='0'.repeat(64)}],
['different old lines',s=>{s.context.pending!.editDigest.oldLineHashes=['0'.repeat(64)]}],
['different new lines',s=>{s.context.pending!.editDigest.newLineHashes=['0'.repeat(64)]}],
['changed old request',s=>{current(s).input!.old_string+=' '}],
['changed replacement',s=>{current(s).input!.new_string+=' '}],
['missing current file',s=>{fs.unlinkSync(s.file)}],
['changed current file with old mtime',s=>{fs.writeFileSync(s.file,fixture.before+'\nChanged.');fs.utimesSync(s.file,new Date(0),new Date(0))}],
['file updated after hook',s=>{fs.utimesSync(s.file,new Date(fixture.now),new Date(fixture.now))}],
['stale viewport',s=>{s.context.viewportCapturedAt=Date.parse(s.hook.pending.timestamp)-1}],
['stale hook',s=>{s.context.pending!.timestamp=new Date(fixture.commandStartedAt-1).toISOString()}],
['future viewport',s=>{s.context.viewportCapturedAt=fixture.now+1}],
['unavailable native',s=>{s.context.transcriptStatus='missing'}],
]);
const s=setup();try{
expect(s.invoke(fixture.viewport,s.context,new Set([s.hook.sessionId+':'+s.hook.pending.toolUseId]))).toBeNull();
expect(s.invoke(fixture.viewport,s.context,new Set([permission.autoplanArtifactMenuKey(fixture.viewport)]))).toBeNull();
}finally{s.dispose()}
});
test('invalid, busy, foreign or ambiguous persisted hook state supplies no current authority',()=>{
for(const change of [
(s:Replay)=>{fs.writeFileSync(s.hookFile+'.invalid','{"reason":"conflicting_replay"}')},
(s:Replay)=>{fs.writeFileSync(s.hookFile+'.lock','')},
(s:Replay)=>{s.hook.pending.transcriptPath=path.join(s.dir,'foreign.jsonl');fs.writeFileSync(s.hookFile,JSON.stringify(s.hook))},
(s:Replay)=>{s.hook.pending.hookSeenIds=[];fs.writeFileSync(s.hookFile,JSON.stringify(s.hook))},
]){const s=setup();try{change(s);expect(readPendingAutoplanArtifact(s.hookFile,s.cwd,s.config,s.stateRoot,fixture.commandStartedAt,s.publicTools,fixture.now,true)).toBeUndefined()}finally{s.dispose()}}
});
test('existing prefix forms compose but cannot hide a competing title, source or malformed current panel',()=>{
const s=setup();try{
const first=fixture.viewport.indexOf('● Update('),screen=fixture.viewport.slice(first);
const titles=screen.match(/^● Update\([^\n]+\)\n/gm)!;
expect(titles).toHaveLength(4);
expect(s.invoke(screen)?.input).toBe('1\r');
expect(s.invoke('\n\n'+screen)?.input).toBe('1\r');
expect(s.invoke(fixture.viewport.replaceAll(titles[0]!,''))).toBeNull(); // A completed prefix still needs its current tool boundary.
expect(s.invoke(screen.slice(screen.indexOf('────────────────')))?.input).toBe('1\r');
let one=screen;for(let n=0;n<3;n++)one=one.replace(titles[0]!,'');
expect(s.invoke(one.trimStart())?.input).toBe('1\r');
for(const [name,changed] of [
['foreign first title',fixture.viewport.replace(titles[0]!,titles[0]!.replace('user-dashboard.md','foreign.md'))],
['quoted whole pane',fixture.viewport.split('\n').map(row=>'> '+row).join('\n')],
['source prefix','Example:\n'+fixture.viewport],
['arbitrary indented prose',' This is an example.\n'+fixture.viewport],
['competing completed panel','● Update(/tmp/foreign.md)\n'+fixture.viewport],
['broken wrap kind',fixture.viewport.replace(/^ \+/m,' -')],
['foreign displayed path',fixture.viewport.replace('…2101964-HvDZyN','…foreign')],
['wrong menu target',fixture.viewport.replace('user-dashboard.md?','foreign.md?')],
['persistent edit mode',fixture.viewport.replace('❯ 1. Yes','❯ 2. Yes')],
['malformed no',fixture.viewport.replace('3. No','3. Maybe')],
['changed addition',fixture.viewport.replace(/^( {0,3}\d+ \+).*/m,'$1A different current edit')],
])expect(s.invoke(changed),name).toBeNull();
}finally{s.dispose()}
});
test('the new queue regression files select only the Autoplan owner with dense registration',()=>{
const owner=E2E_TOUCHFILES['autoplan-chain-pty']!;
for(let i=0;i<owner.length;i++){expect(Object.hasOwn(owner,i)).toBe(true);expect(typeof owner[i]).toBe('string')}
for(const file of ['test/autoplan-edit-queue-am.test.ts','test/fixtures/autoplan-edit-queue-am.json'])
expect(selectTests([file],E2E_TOUCHFILES,[]).selected).toEqual(['autoplan-chain-pty']);
});