2026-09-10 18:27:41 -07:00
import test from "node:test" ;
import assert from "node:assert/strict" ;
import fs from "node:fs/promises" ;
import os from "node:os" ;
import path from "node:path" ;
import { promisify } from "node:util" ;
import { execFile as callback } from "node:child_process" ;
import { planPinUpgrades , applyPinUpgrades , type UpgradeEffects } from "../src/capability-language/pin-upgrades.js" ;
const execFile = promisify ( callback );
2026-09-14 10:26:11 -07:00
test ( "real jj snapshots and immutable Git publication propagate a changed interface into the root" , { skip : ! process . env . QX_CHECK_GENERATOR }, async ( context ) => {
2026-09-13 22:07:18 -07:00
const workbench = await fs . mkdtemp ( path . join ( os . tmpdir (), "qx-real-upgrade-" ));
context . after (() => fs . rm ( workbench , { recursive : true , force : true }));
// Exercise the actual effects with local bare remotes, without external writes.
const environment = {
GIT_CONFIG_COUNT : "1" , GIT_CONFIG_KEY_0 : `url.file:// ${ workbench } /remotes/.insteadOf` ,
GIT_CONFIG_VALUE_0 : "https://upgrade.test/" , QUIXOS_JJ_NO_CHECKPOINT : "1" ,
2026-09-14 10:26:11 -07:00
QUIXOS_CHECK_GENERATOR : process.env.QX_CHECK_GENERATOR ! ,
2026-09-13 22:07:18 -07:00
};
const previous = Object . fromEntries ( Object . keys ( environment ). map ( key => [ key , process . env [ key ]]));
Object . assign ( process . env , environment );
context . after (() => { for ( const [ key , value ] of Object . entries ( previous )) if ( value === undefined ) delete process . env [ key ]; else process . env [ key ] = value ;});
const run = async ( cwd : string , command : string , args : string []) => ( await execFile ( command , args , { cwd })). stdout . trim ();
await fs . mkdir ( path . join ( workbench , "remotes" ));
const nodes = [];
for ( const [ kind , directory , remote ] of [[ "interface" , "resources/Named" , "named.git" ], [ "workspace" , "root" , "workspace.git" ]] as const ) {
const root = path . join ( workbench , directory );
await fs . mkdir ( root , { recursive : true });
await run ( workbench , "git" , [ "init" , "--bare" , path . join ( workbench , "remotes" , remote )]);
await run ( root , "jj" , [ "git" , "init" , "--colocate" ]);
await run ( root , "git" , [ "remote" , "add" , "origin" , `https://upgrade.test/ ${ remote } ` ]);
await fs . writeFile ( path . join ( root , ".gitignore" ), ".quixos/\n" );
const child = nodes [ 0 ];
await fs . writeFile ( path . join ( root , "quixos.lock" ), `quixos-lock version 1 { quixos source { repository "https://upgrade.test/quixos.git"; commit " ${ "a" . repeat ( 40 ) } "; } ${ child ? `interface Named source { repository " ${ child . source . repository } "; commit " ${ child . source . commit } "; }` : "" } }` );
await fs . writeFile ( path . join ( root , ` ${ kind } .qx` ), kind === "interface" ? 'interface Named id "interface:named" revision "interface:named@1" {}' : `workspace W id "workspace:w" revision "workspace:w@1" commit " ${ "a" . repeat ( 40 ) } " { import interface Named; atom A id "atom:a"; }` );
await run ( root , "jj" , [ "describe" , "-m" , "Initial source" ]);
const commit = await run ( root , "jj" , [ "log" , "--no-graph" , "-r" , "@" , "-T" , "commit_id" ]);
await run ( root , "git" , [ "push" , "origin" , ` ${ commit } :refs/tags/quixos-reachability/ ${ commit } ` ]);
nodes . push ({ kind , directory , source : { repository : `https://upgrade.test/ ${ remote } ` , commit }});
}
await fs . mkdir ( path . join ( workbench , ".quixos" ));
await fs . writeFile ( path . join ( workbench , ".quixos/resource-graph.json" ), JSON . stringify ({ resources : [ nodes [ 0 ]]}));
await fs . appendFile ( path . join ( workbench , nodes [ 0 ]. directory , "interface.qx" ), "\n// incremental author edit\n" );
const plan = await planPinUpgrades ( workbench , { nodes , bootstrap : true });
const result = await applyPinUpgrades ( plan );
assert . equal ( result . activated , false );
const graph = JSON . parse ( await fs . readFile ( path . join ( workbench , ".quixos/resource-graph.json" ), "utf8" ));
const commit = graph . resources [ 0 ]. source . commit ;
assert . notEqual ( commit , nodes [ 0 ]. source . commit );
assert . match ( await fs . readFile ( path . join ( workbench , "root/quixos.lock" ), "utf8" ), new RegExp ( commit ));
assert . match ( await run ( workbench , "git" , [ "--git-dir" , path . join ( workbench , "remotes/named.git" ), "show-ref" ]), new RegExp ( commit ));
});
2026-09-10 18:27:41 -07:00
test ( "pin upgrades publish children before parent locks and resume without republishing completed nodes" , async ( context ) => {
const workbench = await fs . mkdtemp ( path . join ( os . tmpdir (), "qx-upgrade-test-" ));
context . after (() => fs . rm ( workbench , { recursive : true , force : true }));
const from = "a" . repeat ( 40 ), to = "b" . repeat ( 40 ), framework = "c" . repeat ( 40 );
const sources = [{ kind : "workspace" as const , directory : "root" , source : { repository : "https://example.test/workspace.git" , commit : from }},
{ kind : "interface" as const , directory : "resources/Named" , source : { repository : "https://example.test/named.git" , commit : from }}];
const lock = `quixos-lock version 1 { quixos source { repository "https://example.test/quixos.git"; commit " ${ framework } "; }` ;
for ( const node of sources ) {
const directory = path . join ( workbench , node . directory );
await fs . mkdir ( directory , { recursive : true });
await execFile ( "git" , [ "-C" , directory , "init" ]);
await execFile ( "git" , [ "-C" , directory , "remote" , "add" , "origin" , node . source . repository ]);
await fs . writeFile ( path . join ( directory , ".gitignore" ), ".quixos/\n" );
await fs . writeFile ( path . join ( directory , "quixos.lock" ), lock + ( node . kind === "workspace" ? ` interface Named source { repository " ${ sources [ 1 ]. source . repository } "; commit " ${ from } "; }` : "" ) + " }" );
}
await fs . writeFile ( path . join ( workbench , "resources/Named/interface.qx" ), 'interface Named id "interface:named" revision "interface:named@1" { value name id "member:name" : string { get id "op:get"; } }' );
await fs . writeFile ( path . join ( workbench , "root/workspace.qx" ), `workspace W id "workspace:w" revision "workspace:w@1" commit " ${ from } " { import interface Named; atom A id "atom:a"; }` );
const snapshotMap = path . join ( workbench , "snapshots.json" );
await fs . writeFile ( snapshotMap , JSON . stringify ({ resources : [{ kind : "interface" , repository : sources [ 1 ]. source . repository , commit : to , directory : path.join ( workbench , "resources/Named" )}]}));
const oldMap = process . env . QUIXOS_SNAPSHOT_MAP ;
process . env . QUIXOS_SNAPSHOT_MAP = snapshotMap ;
context . after (() => { if ( oldMap === undefined ) delete process . env . QUIXOS_SNAPSHOT_MAP ; else process . env . QUIXOS_SNAPSHOT_MAP = oldMap ;});
const plan = await planPinUpgrades ( workbench , { nodes : sources });
await fs . mkdir ( path . join ( workbench , ".quixos" ), { recursive : true });
await fs . writeFile ( path . join ( workbench , ".quixos/resource-graph.json" ), JSON . stringify ({ resources : [{... sources [ 1 ], source : { resolver : "git" , ... sources [ 1 ]. source }}]}));
assert . deepEqual ( plan . nodes . map (( node ) => node . directory ), [ "resources/Named" , "root" ]);
let fail = true ;
const published : string [] = [];
const effects : UpgradeEffects = {
check : async ( node ) => { if ( node . kind === "workspace" && fail ) throw new Error ( "refactor required" );},
snapshot : async () => to ,
publish : async ( root ) => { published . push ( path . relative ( workbench , root ));},
};
await assert . rejects (() => applyPinUpgrades ( plan , undefined , effects ), /refactor required/ );
assert . deepEqual ( published , [ "resources/Named" ]);
assert . match ( await fs . readFile ( path . join ( workbench , "root/quixos.lock" ), "utf8" ), new RegExp ( to ));
const id = ( await fs . readdir ( path . join ( workbench , ".quixos/upgrades" ))). find (( name ) => name . endsWith ( ".json" )) ! . slice ( 0 , - 5 );
fail = false ;
await fs . appendFile ( path . join ( workbench , "root/workspace.qx" ), "\n// explicit refactor\n" );
await assert . rejects (() => applyPinUpgrades ( plan , id , effects ), /accept-edits/ );
const result = await applyPinUpgrades ( plan , id , effects , { acceptEdits : true });
assert . equal ( result . activated , false );
assert . deepEqual ( published , [ "resources/Named" , "root" ]);
const graph = JSON . parse ( await fs . readFile ( path . join ( workbench , ".quixos/resource-graph.json" ), "utf8" ));
assert . equal ( graph . resources . length , 1 );
assert . equal ( graph . resources [ 0 ]. source . commit , to );
const next = await planPinUpgrades ( workbench , { nodes : [ sources [ 0 ], {... sources [ 1 ], source : graph.resources [ 0 ]. source }]});
assert . deepEqual ( next . nodes . find (( node ) => node . kind === "workspace" ) ! . dependencies , [ "resources/Named" ]);
});