app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598
//! End-to-end verb suite against the REAL binary in mock mode.
//!
//! This is the port of `tests/test_verbs.py`, plus the gates the Rust port owes
//! the brief: verb coverage measured from calls actually made, response-shape
//! invariants on every call (in the harness), numbered error paths, and effects
//! asserted on disk rather than "the response said it worked".
//!
//! It runs as ONE test function on purpose: the verbs share a stateful mock
//! application (open documents, active doc), so their ORDER is part of what is
//! under test — and one server boot keeps the suite fast.
mod common;
use common::{manifest, manifest_verbs, Bridge, TempDir};
use serde_json::{json, Value};
fn s(v: &Value) -> String {
serde_json::to_string(v).unwrap_or_default()
}
#[test]
fn every_verb_over_http_against_the_real_binary() {
// A MACHINE THIS PROCESS CAN LOOK AT. The safe-close sweep refuses to close
// anything while the window census is unreadable — a dialog nobody can see is
// the one that matters — so a walk that ends in a real close needs a session
// the bridge can actually see. Without it this test would exercise the REFUSAL
// and silently stop covering the close.
let machine = TempDir::new("e2e-machine");
std::fs::write(machine.join("windows.json"), "[]").unwrap();
let b = Bridge::start_with(&[("SWX_LAUNCH_FAKE_DIR", &machine.path())]);
// ── health endpoints ────────────────────────────────────────────────────
let (status_code, health) = b.get("/status");
check!(b, "GET /status is 200", status_code == 200, s(&health));
check!(b, "health status ok", health["status"] == "ok", s(&health));
check!(b, "health mock flag", health["mock"] == true, s(&health));
check!(b, "health led", health["led"] == "yellow", s(&health));
check!(
b,
"health summary + tooltip are present for the status chip",
health["summary"].is_string() && health["tooltip"].is_string(),
s(&health)
);
check!(
b,
"health version matches the manifest",
health["bridgeVersion"] == manifest()["version"],
format!("{} vs {}", health["bridgeVersion"], manifest()["version"])
);
let (_, alias) = b.get("/health");
check!(b, "/health mirrors /status", alias["status"] == "ok", s(&alias));
let (missing, _) = b.get("/nope");
check!(b, "unknown path is 404", missing == 404, missing.to_string());
// ── malformed input: a clean 400, never a 500 ───────────────────────────
let (code, body) = b.post_raw("{not json");
check!(b, "malformed body is 400", code == 400, format!("{code} {body}"));
check!(b, "malformed body answers JSON", body.contains("Invalid JSON"), body.clone());
// ── core ────────────────────────────────────────────────────────────────
let r = b.call("swx_describe", json!({}));
let described: Vec<String> = r["verbs"]
.as_array()
.expect("describe verbs[]")
.iter()
.map(|v| v["verb"].as_str().unwrap().to_string())
.collect();
check!(b, "describe lists the whole v0.3 surface", described.len() >= 60, s(&r));
check!(b, "describe advertises the modeling ops", r["scriptOps"].as_array().unwrap().contains(&json!("extrude")), s(&r));
check!(
b,
"describe carries the LIVE-VERIFIED export format matrix",
r["exportFormats"]["provenModelFormats"].as_array().unwrap().contains(&json!("glb"))
&& r["exportFormats"]["blockedFormats"].as_array().unwrap().contains(&json!("wrl")),
s(&r)
);
check!(b, "describe keyword", r["keyword"] == "swx", s(&r));
check!(
b,
"describe carries a risk per verb",
r["verbs"].as_array().unwrap().iter().all(|v| v["risk"].is_string()),
s(&r)
);
let r = b.call("swx_readiness", json!({}));
check!(b, "readiness ready (mock)", r["ready"] == true, s(&r));
let r = b.call("swx_status", json!({}));
check!(b, "status success", r["success"] == true, s(&r));
check!(b, "status reports the com host pid", r["com"]["hostPid"].is_u64(), s(&r));
check!(b, "status etiquette", r["windowEtiquette"] == "background", s(&r));
let r = b.call("swx_progress", json!({}));
check!(b, "progress idle", r["active"] == json!([]), s(&r));
let r = b.call("swx_nonsense", json!({}));
check!(
b,
"unknown verb is hinted with the valid list",
r["_hint"].as_str().unwrap_or("").contains("Valid swx commands"),
s(&r)
);
check!(b, "unknown verb invites a bug report", r["_reportIssues"].is_string(), s(&r));
// ── app lifecycle ───────────────────────────────────────────────────────
let r = b.call("swx_docs", json!({}));
check!(b, "docs before start errors", r["success"] == false, s(&r));
check!(b, "a failure carries a hint", r["_hint"].is_string(), s(&r));
let r = b.call("swx_start", json!({}));
check!(b, "start", r["success"] == true && r["started"] == true, s(&r));
check!(b, "start progress is terminal", r["progress"]["percent"] == 100, s(&r));
check!(b, "start progress reports elapsed", r["progress"]["elapsedSec"].is_number(), s(&r));
check!(b, "start etiquette", r["windowEtiquette"] == "background", s(&r));
// ── documents ───────────────────────────────────────────────────────────
let r = b.call("swx_open", json!({"path": "C:\\mock\\gearbox.sldasm"}));
check!(b, "open assembly", r["success"] == true && r["opened"]["type"] == "assembly", s(&r));
let r = b.call("swx_open", json!({"path": "C:\\mock\\readme.txt"}));
check!(b, "open rejects a non-SOLIDWORKS file", r["success"] == false, s(&r));
let r = b.call("swx_open", json!({}));
check!(b, "open without a path is rejected", r["success"] == false, s(&r));
let r = b.call("swx_docs", json!({}));
check!(b, "docs lists the open documents", !r["docs"].as_array().unwrap().is_empty(), s(&r));
check!(b, "docs active is the assembly", r["active"]["type"] == "assembly", s(&r));
// ── properties ──────────────────────────────────────────────────────────
// props_set MODIFIES the document — it leaves it dirty, so the user's own
// Ctrl+S would commit bridge-authored data to their file. Gated like every
// other write; "in memory until a gated save" ignored the ungatable
// persist route, which is the user.
let r = b.call("swx_props_set", json!({"properties": {"PartNo": "ASM-9", "Rev": "B"}}));
check!(
b,
"swx_props_set refuses to modify a document the bridge did not create",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
// On a document the bridge made, it is the normal case.
b.call("swx_new_document", json!({"type": "part"}));
let r = b.call("swx_props_set", json!({"properties": {"PartNo": "ASM-9", "Rev": "B"}}));
check!(b, "props_set", r["success"] == true && r["set"]["Rev"] == 1, s(&r));
let r = b.call("swx_props_get", json!({}));
check!(b, "props_get roundtrip", r["properties"]["Rev"]["resolved"] == "B", s(&r));
b.call("swx_close_doc", json!({"save": false}));
b.call("swx_activate_document", json!({"name": "gearbox.sldasm"}));
let r = b.call("swx_props_set", json!({"properties": {}}));
check!(b, "props_set with no properties is rejected", r["success"] == false, s(&r));
// swx_save is the PERSIST step, so it is gated on a document the bridge did
// not create — swx_props_set is not, because nothing it does reaches the
// user's file until this verb runs.
let r = b.call("swx_save", json!({}));
check!(b, "swx_save refuses to persist into the user's own document", r["errorCode"] == "user_writes_disabled", s(&r));
check!(b, "...and reports the posture it is in", r["userWrites"] == "disabled", s(&r));
// Confirm + reason are NOT enough on their own any more: the process-level
// opt-in is the part an AI cannot grant itself.
let r = b.call("swx_save", json!({"allowUserWrites": true, "confirm": true, "reason": "the user asked"}));
check!(
b,
"swx_save: arguments alone cannot unlock a user write",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
// ── BOM (and its CSV, on disk) ──────────────────────────────────────────
let r = b.call("swx_bom", json!({"format": "csv"}));
check!(b, "bom rows", r["rowCount"] == 3, s(&r));
check!(
b,
"bom quantities aggregate",
r["rows"].as_array().unwrap().iter().any(|row| row["quantity"] == 8),
s(&r)
);
check!(
b,
"bom rows are sorted by name",
r["rows"][0]["name"] == "bracket" && r["rows"][2]["name"] == "shaft",
s(&r)
);
let csv = r["csv"].as_str().unwrap_or("").to_string();
check!(b, "bom csv content", csv.contains("m4-screw"), csv.clone());
check!(b, "bom csv property columns", csv.contains("BRK-100"), csv.clone());
let csv_path = r["csvPath"].as_str().unwrap_or("").to_string();
check!(b, "bom csv file written", std::path::Path::new(&csv_path).is_file(), csv_path.clone());
check!(
b,
"bom csv landed in the bridge's own scratch dir",
csv_path.starts_with(&b.scratch.path()),
format!("{csv_path} not under {}", b.scratch.path())
);
// ...and it really is CSV, with the union of property names as columns.
let on_disk = std::fs::read_to_string(&csv_path).expect("read csv");
let lines: Vec<&str> = on_disk.lines().collect();
check!(b, "csv has a header + 3 rows", lines.len() == 4, on_disk.clone());
check!(
b,
"csv header carries both property columns",
lines[0] == "item,name,configuration,quantity,file,Material,PartNo",
lines[0].to_string()
);
check!(
b,
"csv rows have one cell per header column",
lines.iter().all(|l| l.split(',').count() == 7),
on_disk.clone()
);
let r = b.call("swx_bom", json!({"path": "C:\\mock\\gearbox.sldasm", "topLevelOnly": true}));
check!(b, "bom topLevelOnly is echoed back", r["topLevelOnly"] == true, s(&r));
b.call("swx_open", json!({"path": "C:\\mock\\bracket.sldprt"}));
let r = b.call("swx_bom", json!({}));
check!(b, "bom rejects a part", r["success"] == false, s(&r));
// ── the self-test (0.10.0): read level changes nothing ───────────────────
let docs_before = b.call("swx_docs", json!({}))["count"].clone();
let r = b.call("swx_selftest", json!({}));
check!(
b,
"swx_selftest (read) reports one row per check, with the version facts, and changes nothing",
r["success"] == true && r["level"] == "read" && r["results"].as_array().map(|a| a.len() >= 4).unwrap_or(false)
&& r["solidworks"]["versionSupport"].is_object() && b.call("swx_docs", json!({}))["count"] == docs_before,
s(&r)
);
// ── analysis ────────────────────────────────────────────────────────────
let r = b.call("swx_mass_properties", json!({}));
check!(
b,
"mass properties",
r["success"] == true && r["massKg"].as_f64().unwrap_or(0.0) > 0.0,
s(&r)
);
let r = b.call("swx_interference", json!({"path": "C:\\mock\\gearbox.sldasm"}));
check!(b, "interference", r["count"] == 1, s(&r));
let r = b.call("swx_interference", json!({"path": "C:\\mock\\bracket.sldprt"}));
check!(b, "interference rejects a part", r["success"] == false, s(&r));
// rebuild_check calls ForceRebuild3 — it re-solves features and dirties the
// document, which this bridge's own com_eval hazard list already named as
// "rebuilds the user's model".
let r = b.call("swx_rebuild_check", json!({}));
check!(
b,
"swx_rebuild_check refuses to rebuild a document the bridge did not create",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
b.call("swx_new_document", json!({"type": "part"}));
let r = b.call("swx_rebuild_check", json!({}));
check!(b, "rebuild check", r["errorCount"] == 0, s(&r));
b.call("swx_close_doc", json!({"save": false}));
b.call("swx_activate_document", json!({"name": "bracket.sldprt"}));
// ── export + Pack and Go: bytes on disk, not claims ─────────────────────
let tmp = TempDir::new("out");
let out = tmp.join("bracket.step");
let r = b.call("swx_export", json!({"format": "step", "out": out.to_string_lossy()}));
check!(b, "export step", r["success"] == true && out.is_file(), s(&r));
let bytes = std::fs::read(&out).expect("read export");
check!(b, "export wrote real bytes", !bytes.is_empty(), format!("{} bytes", bytes.len()));
check!(
b,
"export byte count is reported honestly",
r["bytes"].as_u64() == Some(bytes.len() as u64),
s(&r)
);
let r = b.call("swx_export", json!({"format": "wat"}));
check!(b, "export rejects an unsupported format", r["success"] == false, s(&r));
// A relay caller must not aim `out` at a relative / traversal path — that
// would resolve against the bridge's cwd (an escape off the desktop).
let r = b.call("swx_export", json!({"format": "step", "out": "../../victim.txt"}));
check!(
b,
"export refuses a relative traversal out",
r["errorCode"] == "unsafe_path",
s(&r)
);
let r = b.call("swx_bom", json!({"path": "C:\\mock\\gearbox.sldasm", "format": "csv", "out": "escape.csv"}));
check!(b, "bom csv refuses a relative out", r["errorCode"] == "unsafe_path", s(&r));
// No `out`: the file must land in the bridge's own pruned scratch dir.
let r = b.call("swx_export", json!({"format": "iges", "path": "C:\\mock\\bracket.sldprt"}));
let auto = r["exported"].as_str().unwrap_or("").to_string();
check!(b, "export without out succeeds", r["success"] == true, s(&r));
check!(
b,
"export without out lands in the scratch dir with the right extension",
auto.starts_with(&b.scratch.path()) && auto.ends_with(".igs"),
auto.clone()
);
check!(b, "auto-named export exists", std::path::Path::new(&auto).is_file(), auto.clone());
// Pack and Go copies the document AND its whole reference tree — a bulk
// extraction of the user's design, not one export — so it needs to be aimed
// at a document the bridge created. (The server used to skip the ownership
// question entirely for an empty destination while the real com host
// refused, so this verb was dead on a real machine and green here.)
let png_dir = tmp.join("packandgo");
let r = b.call(
"swx_pack_and_go",
json!({"path": "C:\\mock\\gearbox.sldasm", "out": png_dir.to_string_lossy()}),
);
check!(
b,
"swx_pack_and_go refuses to collect a document the bridge did not create",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
check!(
b,
"...and left no directory behind after the refusal",
!png_dir.exists(),
png_dir.display().to_string()
);
b.call("swx_new_document", json!({"type": "assembly"}));
let r = b.call("swx_pack_and_go", json!({"out": png_dir.to_string_lossy()}));
check!(b, "pack and go", r["success"] == true, s(&r));
let files = r["files"].as_array().cloned().unwrap_or_default();
check!(b, "pack and go lists files", !files.is_empty(), s(&r));
check!(
b,
"pack and go files exist on disk",
files.iter().all(|f| std::path::Path::new(f.as_str().unwrap_or("")).is_file()),
s(&r)
);
let r = b.call("swx_pack_and_go", json!({}));
check!(b, "pack and go without out is rejected", r["success"] == false, s(&r));
// ── find_files ──────────────────────────────────────────────────────────
let fixtures = TempDir::new("find");
for name in ["a.sldprt", "b.sldasm", "~$b.sldasm", "c.txt"] {
std::fs::write(fixtures.join(name), b"x").expect("fixture");
}
let root = fixtures.path();
let r = b.call("swx_find_files", json!({"roots": [root]}));
check!(b, "find_files finds 2 (and skips the ~$ lock file)", r["count"] == 2, s(&r));
check!(b, "find_files reports the roots it used", r["roots"][0] == root, s(&r));
let r = b.call("swx_find_files", json!({"roots": [root], "types": ["assembly"]}));
check!(
b,
"find_files type filter",
r["count"] == 1 && r["files"][0]["type"] == "assembly",
s(&r)
);
let r = b.call("swx_find_files", json!({"roots": [root], "query": "a.sld"}));
check!(b, "find_files query filter", r["count"] == 1, s(&r));
let r = b.call("swx_find_files", json!({"roots": [root], "limit": 1}));
check!(b, "find_files limit truncates honestly", r["count"] == 1 && r["truncated"] == true, s(&r));
// ══ v0.3: the Fusion-parity surface ═════════════════════════════════════
// State on entry: gearbox.sldasm and bracket.sldprt are open; bracket is
// active. Order matters — these verbs share the mock's stateful app.
let r = b.call("swx_get_app_state", json!({}));
check!(b, "app_state running", r["running"] == true, s(&r));
check!(b, "app_state lists the open documents", r["documents"].as_array().unwrap().len() >= 2, s(&r));
check!(b, "app_state names the active document", r["activeDocument"].is_string(), s(&r));
check!(b, "app_state reports the workspace (the active doc's type)", r["workspace"].is_string() && r["workspace"] != "none", s(&r));
b.call("swx_activate_document", json!({"name": "bracket.sldprt"}));
let r = b.call("swx_document_info", json!({}));
check!(b, "document_info names the active doc", r["name"] == "bracket.sldprt", s(&r));
check!(b, "document_info reports it as saved (it has a path)", r["isSaved"] == true, s(&r));
check!(b, "document_info lists configurations", r["configurations"][0] == "Default", s(&r));
// ── create: the verb Drew asked for ─────────────────────────────────────
let r = b.call("swx_new_document", json!({"type": "part"}));
check!(b, "new_document creates a part", r["success"] == true && r["created"]["type"] == "part", s(&r));
let scratch_title = r["created"]["title"].as_str().unwrap_or("").to_string();
check!(
b,
"new_document names it from SOLIDWORKS' own Part<n>, and gives it a FILE",
scratch_title.starts_with("Part") && scratch_title.ends_with(".SLDPRT"),
scratch_title.clone()
);
check!(
b,
"...so it has an identity that is not just a title (0.6.0)",
r["documentIdentity"]["identity"] == "path"
&& r["created"]["path"].as_str().unwrap_or("").contains("scratch"),
s(&r)
);
// 0.6.0: the old claim here was "a brand-new document has NO path until it is
// saved". That was true, and it was the defect — three pathless documents
// sharing one title were addressable by nothing. `keepUnsaved:true` is where
// that state now lives, and it is reported rather than silent.
let bare = b.call("swx_new_document", json!({"type": "part", "keepUnsaved": true}));
check!(
b,
"keepUnsaved:true still makes a pathless document...",
bare["created"]["path"] == "" && bare["documentIdentity"]["identity"] == "titleOnly",
s(&bare)
);
check!(
b,
"...and SAYS that its title is the only name it has",
bare["documentIdentity"]["warning"].as_str().unwrap_or("").contains("ambiguous_target"),
s(&bare)
);
check!(b, "new_document reports the template it used", r["template"].is_string(), s(&r));
let r = b.call("swx_new_document", json!({"type": "sandwich"}));
check!(b, "new_document rejects an unknown type", r["success"] == false, s(&r));
// The bridge WROTE DOWN that it created that document — which is what lets
// swx_reset_workspace clean up after itself without touching user documents.
let r = b.call("swx_action_ledger", json!({}));
check!(
b,
"the action ledger recorded the document the bridge created",
r["actions"].as_array().unwrap().iter().any(|a| a["action"] == "document_created"
&& a["detail"]["title"] == "Part1"),
s(&r)
);
check!(b, "ledger entries are timestamped", r["actions"][0]["at"].as_u64().unwrap_or(0) > 0, s(&r));
let r = b.call("swx_design_info", json!({}));
check!(b, "design_info on the empty scratch part has no bodies", r["bodies"] == 0, s(&r));
// ── macros: the verb whose gate cannot be loosened ──────────────────────
// Full coverage lives in `running_a_macro_is_arbitrary_code_and_is_gated_as_such`;
// this is the sweep's assertion that the DEFAULT posture refuses, which is
// the property that must hold on every bridge that has not been explicitly
// opted in.
let r = b.call("swx_run_macro", json!({"path": "C:\\anything.swp"}));
check!(
b,
"swx_run_macro refuses in the default posture — a macro is arbitrary code",
r["success"] == false,
s(&r)
);
// ── the modeling DSL ────────────────────────────────────────────────────
let r = b.call("swx_run_script", json!({"script": "import adsk; do_stuff()"}));
check!(b, "run_script refuses a string of code", r["errorCode"] == "bad_script", s(&r));
check!(b, "a refused script teaches the grammar", r["ops"].as_array().unwrap().contains(&json!("sketch_rect")), s(&r));
check!(b, "a refused script shows a worked example", r["example"]["script"].is_array(), s(&r));
let r = b.call("swx_run_script", json!({"script": [{"op": "extrude", "depthMm": 5}]}));
check!(b, "run_script refuses an extrude with no sketch", r["errorCode"] == "bad_script", s(&r));
check!(
b,
"the refusal names the step that cannot work",
r["error"].as_str().unwrap_or("").contains("no sketch to consume"),
s(&r)
);
let built = TempDir::new("built");
let part_path = built.join("bracket-built.sldprt");
let r = b.call("swx_run_script", json!({"script": [
{"op": "sketch_rect", "plane": "Front Plane", "widthMm": 50, "heightMm": 30},
{"op": "extrude", "depthMm": 20},
{"op": "set_material", "name": "6061 Alloy"},
{"op": "set_prop", "name": "PartNo", "value": "BRK-100"},
{"op": "rebuild"},
{"op": "zoom_fit"}
]}));
check!(b, "run_script builds", r["success"] == true && r["stepCount"] == 6, s(&r));
check!(b, "run_script reports each step", r["steps"][1]["op"] == "extrude", s(&r));
check!(b, "run_script converts mm at the boundary", r["steps"][0]["result"]["widthMm"] == 50.0, s(&r));
// Verify the GEOMETRY, not the step list.
let r = b.call("swx_inspect_bodies", json!({}));
check!(b, "inspect_bodies sees the solid", r["bodyCount"] == 1, s(&r));
check!(b, "inspect_bodies reports millimetres", r["units"] == "mm", s(&r));
check!(b, "inspect_bodies reports the size that was asked for", r["bodies"][0]["sizeMm"]["x"] == 50.0, s(&r));
check!(b, "inspect_bodies reports a volume", r["bodies"][0]["volumeMm3"].as_f64().unwrap_or(0.0) > 0.0, s(&r));
let r = b.call("swx_inspect_bodies", json!({"path": "C:\\mock\\gearbox.sldasm"}));
check!(b, "inspect_bodies rejects an assembly", r["success"] == false, s(&r));
// ── assembly components: the verb the BOM needs ─────────────────────────
// Round-trip proof that swx_add_component -> swx_bom is a real chain: build
// a part FILE, make an assembly, insert the file twice, and read the
// quantity back out of the BOM.
let asmdir = TempDir::new("asm");
let comp_a = asmdir.join("rail.sldprt");
let comp_b = asmdir.join("screw.sldprt");
// Everything this block opens is closed again at the end, and the document
// that was active when it started is put back — the checks after it target
// the active document.
let was_active = b.call("swx_docs", json!({}))["active"]["title"].as_str().unwrap_or("").to_string();
let mut opened: Vec<String> = Vec::new();
for (path, no) in [(&comp_a, "R1"), (&comp_b, "F-10x3")] {
let made = b.call("swx_run_script", json!({"script": [
{"op": "new_part"},
{"op": "sketch_rect", "widthMm": 44.45, "heightMm": 38.1},
{"op": "extrude", "depthMm": 100},
{"op": "set_prop", "name": "PartNo", "value": no},
{"op": "save_as", "path": path.to_string_lossy()}
]}));
if let Some(t) = made["created"]["created"]["title"].as_str() {
opened.push(t.to_string());
}
}
let asm = b.call("swx_new_document", json!({"type": "assembly"}));
let asm_title = asm["created"]["title"].as_str().unwrap_or("Assem1").to_string();
let r = b.call("swx_add_component", json!({"targetDocument": asm_title}));
check!(
b,
"swx_add_component refuses a request with no component file",
r["success"] == false && r["error"].as_str().unwrap_or("").contains("filePath"),
s(&r)
);
let r = b.call("swx_add_component", json!({
"targetDocument": asm_title,
"components": [{"filePath": "RELATIVE.sldprt"}]
}));
check!(
b,
"swx_add_component refuses a relative component path",
r["success"] == false && r["error"].as_str().unwrap_or("").contains("ABSOLUTE"),
s(&r)
);
let r = b.call("swx_add_component", json!({
"targetDocument": asm_title,
"components": [
{"filePath": comp_a.to_string_lossy(), "xMm": 0, "yMm": 0, "zMm": 0},
{"filePath": comp_b.to_string_lossy(), "xMm": 100, "yMm": 0, "zMm": 25.4},
{"filePath": comp_b.to_string_lossy(), "xMm": 200, "yMm": 0, "zMm": 25.4, "rotZDeg": 90},
{"filePath": asmdir.join("missing.sldprt").to_string_lossy()}
]
}));
check!(b, "swx_add_component inserts a batch in one call", r["insertedCount"] == 3, s(&r));
check!(b, "swx_add_component reports the COM call that worked", r["method"].is_string(), s(&r));
check!(
b,
"swx_add_component reports the one that could not go in, and why",
r["failedCount"] == 1
&& r["failed"][0]["error"].as_str().unwrap_or("").contains("not found"),
s(&r)
);
check!(b, "swx_add_component echoes the position in millimetres", r["inserted"][1]["positionMm"][2] == 25.4, s(&r));
check!(
b,
"swx_add_component orients a component that cannot go in axis-aligned",
r["rotationsRequested"] == 1 && r["placedCount"] == 3 && r["inserted"][2]["rotationDeg"][2] == 90.0,
s(&r)
);
check!(b, "swx_add_component reports the assembly's component count", r["componentCount"] == 3, s(&r));
// THE POINT OF THE VERB: a BOM built from what was inserted.
let r = b.call("swx_bom", json!({"targetDocument": asm_title}));
check!(b, "swx_bom sees the inserted components", r["rowCount"] == 2, s(&r));
let screw = r["rows"]
.as_array()
.and_then(|rows| rows.iter().find(|row| row["name"] == "screw"))
.cloned()
.unwrap_or(json!({}));
check!(b, "swx_bom aggregates repeated instances into a quantity", screw["quantity"] == 2, s(&screw));
check!(
b,
"swx_bom carries the components' custom properties",
screw["properties"]["PartNo"]["value"] == "F-10x3",
s(&screw)
);
let r = b.call("swx_add_component", json!({
"path": "C:\\mock\\bracket.sldprt",
"components": [{"filePath": comp_a.to_string_lossy()}]
}));
check!(
b,
"swx_add_component refuses a document the bridge did not create",
r["success"] == false && r["errorCode"] == "user_writes_disabled",
s(&r)
);
b.call("swx_close_doc", json!({"name": asm_title, "save": false}));
for t in &opened {
b.call("swx_close_doc", json!({"name": t, "save": false}));
}
let r = b.call("swx_activate_document", json!({"name": was_active.clone()}));
check!(
b,
"the component block leaves the bench as it found it",
r["activeDocument"]["title"] == json!(was_active.clone()),
s(&r)
);
let r = b.call("swx_design_info", json!({}));
check!(b, "design_info now sees the body", r["bodies"] == 1, s(&r));
check!(b, "design_info reports the material that was set", r["material"]["name"] == "6061 Alloy", s(&r));
// ── parameters ──────────────────────────────────────────────────────────
let r = b.call("swx_get_parameters", json!({}));
check!(
b,
"get_parameters finds the extrusion's named dimension",
r["parameters"].as_array().unwrap().iter().any(|p| p["name"] == "D1@Boss-Extrude1"),
s(&r)
);
check!(
b,
"get_parameters gives both SI and mm",
r["parameters"].as_array().unwrap().iter().any(|p| p["unit"] == "m" && p["valueMm"].is_number()),
s(&r)
);
let r = b.call("swx_set_parameter", json!({"name": "D1@Boss-Extrude1", "valueMm": 25}));
check!(b, "set_parameter drives a dimension in mm", r["success"] == true && r["valueMm"] == 25.0, s(&r));
check!(b, "set_parameter converts mm to SI metres", r["value"] == 0.025, s(&r));
check!(b, "set_parameter shows what it changed FROM", r["before"] == 0.02, s(&r));
let r = b.call("swx_set_parameter", json!({"name": "D1@Nope"}));
check!(b, "set_parameter without a value is rejected", r["success"] == false, s(&r));
let r = b.call("swx_set_parameter", json!({"name": "D1@Nope", "valueMm": 5}));
check!(b, "set_parameter on an unknown dimension is rejected", r["success"] == false, s(&r));
// ── save_as: the scratch document reaches disk ──────────────────────────
let r = b.call("swx_save_as", json!({"outputPath": "relative.sldprt"}));
check!(b, "save_as refuses a relative path", r["errorCode"] == "unsafe_path", s(&r));
let r = b.call("swx_save_as", json!({"outputPath": part_path.to_string_lossy()}));
check!(b, "save_as writes the file", r["success"] == true && part_path.is_file(), s(&r));
check!(
b,
"save_as (not copy) retitles the open document",
r["doc"]["title"].as_str().unwrap_or("").eq_ignore_ascii_case("bracket-built.sldprt"),
s(&r)
);
check!(b, "save_as reports the byte count", r["bytes"].as_u64().unwrap_or(0) > 0, s(&r));
// ── the per-format export aliases (fusion_export_* parity) ──────────────
// The mock refuses exactly the formats a live SOLIDWORKS refuses for a PART
// (swFileSaveError_e 256), so this loop tests both outcomes.
for (verb, ext) in [
("swx_export_step", "step"), ("swx_export_iges", "igs"), ("swx_export_stl", "stl"),
("swx_export_3mf", "3mf"), ("swx_export_glb", "glb"), ("swx_export_sat", "sat"),
("swx_export_parasolid", "x_t"), ("swx_export_pdf", "pdf"),
] {
let out = built.join(&format!("alias.{ext}"));
let r = b.call(verb, json!({"outputPath": out.to_string_lossy()}));
check!(b, &format!("{verb} writes {ext}"), r["success"] == true && out.is_file(), s(&r));
check!(
b,
&format!("{verb} echoes the LIVE-VERIFIED format matrix"),
r["formats"]["provenModelFormats"].is_array(),
s(&r)
);
check!(
b,
&format!("{verb} did NOT rename the open document (Silent|Copy)"),
r["documentRenamed"] == false,
s(&r)
);
check!(b, &format!("{verb} reports a clean save code"), r["saveErrorCode"] == 0, s(&r));
}
// DXF and DWG are DRAWING formats: refused for a part, with the reason.
for (verb, ext) in [("swx_export_dxf", "dxf"), ("swx_export_dwg", "dwg")] {
let out = built.join(&format!("drawingonly.{ext}"));
let r = b.call(verb, json!({"outputPath": out.to_string_lossy()}));
check!(
b,
&format!("{verb} is refused for a PART, as SOLIDWORKS refuses it live"),
r["success"] == false && r["error"].as_str().unwrap_or("").contains("256"),
s(&r)
);
}
// THE guard that shipped broken: it checked the `format` ARGUMENT, but
// SaveAs3 keys off the DESTINATION EXTENSION — so an alias with a hard-coded
// format could name a .wrl output and sail straight past it, into the call
// that hung SOLIDWORKS for 110 seconds. Test the guard the way it BREAKS.
let wrl = built.join("bypass.wrl");
for (verb, args) in [
("swx_export_step", json!({"outputPath": wrl.to_string_lossy()})),
("swx_export_glb", json!({"outputPath": wrl.to_string_lossy()})),
("swx_export", json!({"format": "step", "outputPath": wrl.to_string_lossy()})),
("swx_export", json!({"format": "step", "out": wrl.to_string_lossy()})),
("swx_save_as", json!({"outputPath": wrl.to_string_lossy()})),
] {
let r = b.call(verb, args.clone());
check!(
b,
&format!("{verb} refuses a .wrl DESTINATION whatever the format argument says"),
r["errorCode"] == "format_blocked",
s(&r)
);
}
check!(b, "and nothing was written for any of them", !wrl.exists(), wrl.display().to_string());
// The DSL is the third road to SaveAs3, and it must meet the same rule.
let r = b.call("swx_run_script", json!({"script": [{"op": "save_as", "path": wrl.to_string_lossy()}]}));
check!(b, "the modelling DSL refuses a .wrl destination too", r["errorCode"] == "bad_script", s(&r));
let r = b.call("swx_run_script", json!({"script": [{"op": "save_as", "path": "RELATIVE-BYPASS.sldprt"}]}));
check!(b, "...and a relative DSL save_as, which used to write into the bridge cwd", r["errorCode"] == "bad_script", s(&r));
check!(
b,
"the relative DSL save_as really wrote nothing",
!std::path::Path::new("RELATIVE-BYPASS.sldprt").exists(),
"RELATIVE-BYPASS.sldprt exists in the bridge cwd"
);
// ── import ──────────────────────────────────────────────────────────────
let step_in = built.join("alias.step");
let r = b.call("swx_import", json!({"filePath": step_in.to_string_lossy()}));
check!(b, "import brings a neutral file in as a new document", r["success"] == true, s(&r));
// An imported document reports its CONVERSION TEMP path — what the real
// backend's GetPathName returns — and is never a saved user file. The mock
// used to report "" here, which meant `is_import_temp` (the proof the whole
// drain rests on) was never exercised against a realistic value.
check!(
b,
"the imported document reports the conversion temp path a real one would",
swx_bridge::comhost::host::is_import_temp(r["imported"]["path"].as_str().unwrap_or("")),
s(&r)
);
// Import reports the net document count it added, and says so — one open
// unsaved doc, like new_document (WORKSTREAM A: the leak was callers
// importing without ever closing). See the import_lifecycle suite for the
// enumeration cap and the drain verb in depth.
check!(b, "import asserts exactly one new document", r["documentsOpened"] == 1 && r["netCountOk"] == true, s(&r));
check!(b, "...and its hint says it stays open until closed", r["_hint"].as_str().unwrap_or("").contains("stays open"), s(&r));
let temps = b.call("swx_close_import_temps", json!({"reason": "drain import temps left by this suite"}));
check!(b, "swx_close_import_temps runs in the default posture with just a reason", temps["success"] == true, s(&temps));
check!(b, "...and reports how many it closed and whether it finished", temps["closed"].is_u64() && temps["done"] == true, s(&temps));
let temps_gate = b.call("swx_close_import_temps", json!({}));
check!(b, "...and needs a reason (it closes documents on the user's desktop)", temps_gate["errorCode"] == "reason_required", s(&temps_gate));
let r = b.call("swx_import", json!({}));
check!(b, "import without a filePath is rejected", r["success"] == false, s(&r));
// ...and an imported document is OURS on both sides, so the bridge can
// clean it up. Live run 3 left an orphan because the host never registered
// it: `cmd_import` has three success paths and only one of them did.
// A DISTINCT stem: SOLIDWORKS titles an imported document `<stem>.SLDPRT`,
// so re-importing a file already imported manufactures a title collision.
let step_src = built.join("ownership-probe.step");
b.call("swx_export_step", json!({"outputPath": step_src.to_string_lossy()}));
let imported = b.call("swx_import", json!({"filePath": step_src.to_string_lossy()}));
let it = imported["imported"]["title"].as_str().unwrap_or("").to_string();
check!(b, "swx_import registered the document it created", imported["success"] == true, s(&imported));
let r = b.call("swx_close_doc", json!({"name": it.clone(), "save": false}));
check!(
b,
"the bridge can close a document it imported",
r["success"] == true,
s(&r)
);
// ── activate + the show family ──────────────────────────────────────────
let r = b.call("swx_activate_document", json!({"name": "gearbox.sldasm"}));
check!(b, "activate_document", r["success"] == true && r["activeDocument"]["type"] == "assembly", s(&r));
let r = b.call("swx_activate_document", json!({}));
check!(b, "activate_document without a name is rejected", r["success"] == false, s(&r));
let r = b.call("swx_activate_document", json!({"name": "nothing-open.sldprt"}));
check!(b, "activate_document on a closed document is rejected", r["success"] == false, s(&r));
let r = b.call("swx_show_assembly", json!({}));
check!(b, "show_assembly activates and fits", r["success"] == true && r["zoomedToFit"] == true, s(&r));
check!(b, "show_assembly reports the view", r["view"] == "assembly", s(&r));
let r = b.call("swx_show_part", json!({}));
check!(b, "show_part refuses when the active document is an assembly", r["errorCode"] == "wrong_document_type", s(&r));
let r = b.call("swx_show_drawing", json!({"path": "C:\\mock\\gearbox.sldasm"}));
check!(b, "show_drawing refuses a .sldasm by extension, before opening it", r["errorCode"] == "wrong_document_type", s(&r));
let r = b.call("swx_show_part", json!({"path": "C:\\mock\\newly-shown.sldprt"}));
check!(b, "show_part opens a document it was handed and shows it", r["success"] == true && r["view"] == "part", s(&r));
// ── windows & dialogs: ab is unreachable from the harness, so these must
// degrade honestly rather than throw ──────────────────────────────────
let r = b.call("swx_window_info", json!({}));
check!(b, "window_info degrades honestly when ab is unreachable", r["success"] == true && r["abError"].is_string(), s(&r));
// NOT an empty array. "ab could not be asked" and "SOLIDWORKS has no
// windows" are different facts, and an empty list reads as the second — in
// the live run it did exactly that, and three failures looked like bridge
// bugs when the bridge had simply been started by hand.
check!(b, "...reporting the window list as UNKNOWN, not empty", r["windows"].is_null(), s(&r));
check!(b, "...and saying ab is unavailable", r["abAvailable"] == false, s(&r));
let st = b.call("swx_status", json!({}));
check!(b, "swx_status says up front whether ab is reachable", st["abAvailable"] == false, s(&st));
let r = b.call("swx_dismiss_blocking_dialogs", json!({}));
check!(b, "dismiss_blocking_dialogs reports rather than acting", r["success"] == true, s(&r));
let r = b.call("swx_dismiss_blocking_dialogs", json!({"close": true}));
check!(b, "closing dialogs needs a reason", r["errorCode"] == "reason_required", s(&r));
let r = b.call("swx_dismiss_blocking_dialogs", json!({"close": true, "reason": "an export sheet my own probe raised"}));
check!(b, "closing dialogs needs confirmation too", r["errorCode"] == "confirm_required", s(&r));
let r = b.call("swx_close_window", json!({}));
check!(b, "close_window needs an hwnd", r["success"] == false, s(&r));
let r = b.call("swx_close_window", json!({"hwnd": 4242}));
check!(b, "close_window needs a reason", r["errorCode"] == "reason_required", s(&r));
let r = b.call("swx_close_window", json!({"hwnd": 4242, "reason": "clearing my own dialog"}));
check!(b, "close_window needs confirmation", r["errorCode"] == "confirm_required", s(&r));
let r = b.call("swx_close_window", json!({"hwnd": 4242, "reason": "clearing my own dialog", "confirm": true}));
check!(b, "close_window reports ab being unreachable instead of claiming success", r["success"] == false, s(&r));
// ab is deliberately unreachable in this harness, so the recovery pane —
// which is a WINDOW, not a COM fact — cannot be looked for. The honest
// answer is UNKNOWN. This assertion used to require `false`, which is the
// most misleading possible answer to "did the last session end badly?".
let r = b.call("swx_check_recovery", json!({}));
check!(
b,
"check_recovery says UNKNOWN when the desktop cannot be enumerated, never 'no'",
r["recoveryPending"].is_null() && r["recoveryKnown"] == false && r["abError"].is_string(),
s(&r)
);
let r = b.call("swx_set_driving_badge", json!({}));
check!(b, "the driving badge defaults ON", r["showDrivingBadge"] == true, s(&r));
let r = b.call("swx_set_driving_badge", json!({"enabled": false}));
check!(b, "the driving badge can be turned off", r["showDrivingBadge"] == false, s(&r));
let r = b.call("swx_set_driving_badge", json!({}));
check!(b, "the driving badge setting is STICKY", r["showDrivingBadge"] == false, s(&r));
// ── preferences: written, then READ BACK ────────────────────────────────
let r = b.call("swx_get_preferences", json!({}));
check!(b, "get_preferences lists what can be round-tripped", r["current"]["lengthUnitRaw"]["id"] == 53, s(&r));
// A preference is a change to a DOCUMENT, so it faces the same rule. On the
// user's document it is refused; on the bridge's own it goes through.
let r = b.call("swx_set_preference", json!({"name": "lengthUnitRaw", "value": 3}));
check!(
b,
"swx_set_preference refuses to touch a document the bridge did not create",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
let ours = part_path.to_string_lossy().to_string();
let r = b.call("swx_set_preference", json!({"name": "lengthUnitRaw", "value": 3, "path": ours.clone()}));
check!(b, "set_preference verifies by reading the value back", r["verified"] == true && r["value"] == 3, s(&r));
let r = b.call("swx_get_preferences", json!({"path": ours.clone()}));
check!(b, "the preference really changed", r["current"]["lengthUnitRaw"]["value"] == 3, s(&r));
let r = b.call("swx_set_preference", json!({"name": "theme", "value": 1, "path": ours.clone()}));
check!(b, "set_preference refuses a preference it cannot round-trip", r["success"] == false, s(&r));
let r = b.call("swx_set_preference", json!({}));
check!(b, "set_preference without a name is rejected", r["success"] == false, s(&r));
// ── cloud: the honest refusal ───────────────────────────────────────────
let r = b.call("swx_cloud_status", json!({}));
check!(b, "cloud_status says plainly that there is no cloud route", r["supported"] == false, s(&r));
check!(b, "cloud_status carries the probe that proves it", r["comApiExposesCloud"] == false, s(&r));
check!(b, "cloud_status has a branchable code", r["errorCode"] == "cloud_not_available", s(&r));
// ── measured timings ────────────────────────────────────────────────────
let r = b.call("swx_verb_times", json!({}));
check!(
b,
"verb_times reports what this machine measured",
r["stages"]["swx_run_script"]["count"].as_u64().unwrap_or(0) >= 1,
s(&r)
);
check!(b, "verb_times gives p50 and p90", r["stages"]["swx_run_script"]["p90"].is_number(), s(&r));
// ── the reference diagnostic (0.10.6) ───────────────────────────────────
let docs_before = b.call("swx_docs", json!({}))["docs"].as_array().map(|a| a.len());
let r = b.call("swx_com_respawn", json!({"reason": "release every COM reference the bridge holds"}));
check!(
b,
"swx_com_respawn replaces the bridge's own com host and leaves SOLIDWORKS's documents alone",
r["respawned"] == true && r["solidworksTouched"] == false
&& b.call("swx_docs", json!({}))["docs"].as_array().map(|a| a.len()) == docs_before,
s(&r)
);
// ── OWNERSHIP: the bridge must not change documents it did not create ───
// The suite has a user-owned document open (gearbox.sldasm, opened by this
// test, never created by the bridge). Every mutating verb must refuse it.
let r = b.call("swx_activate_document", json!({"name": "gearbox.sldasm"}));
check!(b, "activate the user's own document for the ownership tests", r["success"] == true, s(&r));
let before = b.call("swx_design_info", json!({"path": "C:\\mock\\gearbox.sldasm"}));
let bodies_before = before["bodies"].clone();
check!(b, "the user's document has a known body count before we try", bodies_before.is_number(), s(&before));
let r = b.call("swx_run_script", json!({"script": [
{"op": "sketch_rect", "widthMm": 10, "heightMm": 10},
{"op": "extrude", "depthMm": 5}
]}));
check!(
b,
"run_script REFUSES to build geometry into a document the bridge did not create",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
check!(b, "...and names what it would have touched", r["targetDocument"] == "gearbox.sldasm", s(&r));
check!(
b,
"...and lists the documents that ARE fair game",
r["bridgeDocuments"].is_array(),
s(&r)
);
let after = b.call("swx_design_info", json!({"path": "C:\\mock\\gearbox.sldasm"}));
check!(
b,
"the user's document was not touched — no body appeared in it",
after["bodies"] == bodies_before,
format!("{} -> {}", bodies_before, after["bodies"])
);
let r = b.call("swx_run_script", json!({
"script": [{"op": "rebuild"}],
"allowUserWrites": true,
"confirm": true,
"reason": "the user asked me to rebuild their gearbox"
}));
check!(
b,
"swx_run_script: even a fully-argued request is refused while user writes are off",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
let r = b.call("swx_set_parameter", json!({"name": "D1@Boss-Extrude1", "valueMm": 9}));
check!(b, "set_parameter refuses a document the bridge did not create", r["errorCode"] == "user_writes_disabled", s(&r));
let r = b.call("swx_save_as", json!({"outputPath": built.join("stolen.sldasm").to_string_lossy()}));
check!(
b,
"save_as refuses to RENAME a document the bridge did not create",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
let r = b.call("swx_save_as", json!({"outputPath": built.join("copy.sldasm").to_string_lossy(), "copy": true}));
check!(b, "...but copy:true is not a rename, so it is allowed", r["success"] == true, s(&r));
// Closing someone else's document at all — dirty or clean — is a change to
// their session, and needs the process-level opt-in.
let r = b.call("swx_close_doc", json!({}));
check!(
b,
"close_doc refuses to close a document the bridge did not create",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
let docs = b.call("swx_docs", json!({}));
check!(
b,
"...and the user's document is still open",
docs["docs"].as_array().unwrap().iter().any(|d| d["title"] == "gearbox.sldasm"),
s(&docs)
);
// close_window must not become a general window killer. With ab
// unreachable the refusal stands, and it now says WHICH refusal it is:
// "the desktop could not be listed" is a different fact from "this window
// is not SOLIDWORKS'", and a caller told the second goes looking for the
// wrong problem.
let r = b.call("swx_close_window", json!({"hwnd": 999999, "reason": "tidy up", "confirm": true}));
check!(
b,
"close_window refuses an hwnd it cannot attribute to SOLIDWORKS",
r["success"] == false
&& (r["errorCode"] == "window_not_attributed"
|| r["errorCode"] == "window_list_unavailable"),
s(&r)
);
check!(
b,
"...and with ab unreachable it says so, rather than blaming the window",
r["errorCode"] == "window_list_unavailable" && r["abError"].is_string(),
s(&r)
);
// ── targetDocument must be REAL: authorize X, act on X ──────────────────
// The defect this replaces: the gate resolved ownership from
// `targetDocument`, but only run_script forwarded it — every other verb sent
// `path` (usually null), so the com host fell back to ActiveDoc. With a user
// part active, `swx_save_as {targetDocument:"Part1"}` renamed and relocated
// the USER's document. The refusal even handed the caller the value to use.
let r = b.call("swx_activate_document", json!({"name": "gearbox.sldasm"}));
check!(b, "swx_activate_document puts the user's document in front", r["success"] == true, s(&r));
let refusal = b.call("swx_save_as", json!({"outputPath": built.join("stolen2.sldasm").to_string_lossy()}));
check!(b, "swx_save_as refuses the user's document", refusal["errorCode"] == "user_writes_disabled", s(&refusal));
let offered = refusal["bridgeDocuments"].as_array().cloned().unwrap_or_default();
check!(b, "the refusal offers the bridge's own documents", !offered.is_empty(), s(&refusal));
check!(
b,
"...each identified by PATH, not by a title that may not be unique",
offered.iter().any(|d| d.get("path").map(|p| p.is_string()).unwrap_or(false)),
s(&refusal)
);
// Retry using the PATH the refusal handed over. It must act on THAT
// document, and the user's must survive with its title AND path unchanged.
let saved = offered
.iter()
.find(|d| d.get("path").map(|p| p.is_string()).unwrap_or(false))
.cloned()
.unwrap_or(Value::Null);
let ours_path = saved["path"].as_str().unwrap_or("").to_string();
let retry_out = built.join("retargeted.sldprt");
let r = b.call("swx_save_as", json!({
"outputPath": retry_out.to_string_lossy(),
"path": ours_path.clone()
}));
check!(b, "swx_save_as retried by path succeeds", r["success"] == true, s(&r));
check!(
b,
"...and it acted on THAT document, not the active one",
r["doc"]["title"].as_str().unwrap_or("") != "gearbox.sldasm",
s(&r)
);
// IDENTITY, not open-ness: same title AND same path.
let docs = b.call("swx_docs", json!({}));
check!(
b,
"the user's document survived with its title AND path identical",
docs["docs"].as_array().unwrap().iter().any(|d| d["title"] == "gearbox.sldasm"
&& d["path"] == "C:\\mock\\gearbox.sldasm"),
s(&docs)
);
// Naming a document two ways, with two different answers, is refused.
let r = b.call("swx_save_as", json!({
"outputPath": built.join("amb.sldprt").to_string_lossy(),
"targetDocument": "gearbox.sldasm",
"path": "C:\\mock\\newly-shown.sldprt"
}));
check!(b, "swx_save_as refuses an ambiguous target", r["errorCode"] == "ambiguous_target", s(&r));
let r = b.call("swx_set_parameter", json!({"name": "D1@X", "valueMm": 1, "targetDocument": "NotOpen.sldprt"}));
check!(b, "swx_set_parameter refuses a targetDocument that is not open", r["errorCode"] == "document_not_open", s(&r));
// ── ownership is scoped to a SOLIDWORKS session, and pruned ─────────────
let before = b.call("swx_reset_workspace", json!({"titles": ["nothing"]}));
check!(b, "swx_reset_workspace reports what it believes it owns", before["trackedScratchDocuments"].is_array(), s(&before));
// ── reset_workspace closes ONLY what the bridge made and never saved ────
let r = b.call("swx_new_document", json!({"type": "assembly"}));
let sweepable = r["created"]["title"].as_str().unwrap_or("").to_string();
check!(
b,
"a second scratch document for the sweep, named the way SOLIDWORKS names one",
sweepable.starts_with("Assem") && !sweepable.starts_with("Assembly"),
sweepable.clone()
);
// The sweep list lives in MEMORY and holds only what this process created.
let r = b.call("swx_reset_workspace", json!({"titles": ["nothing-by-this-name"]}));
check!(b, "reset_workspace with an explicit titles[] closes nothing else", r["closed"] == json!([]), s(&r));
let r = b.call("swx_reset_workspace", json!({}));
check!(b, "reset_workspace closed the scratch document", r["closed"].as_array().unwrap().contains(&json!(sweepable)), s(&r));
check!(
b,
"reset_workspace left the user's own documents alone",
r["skipped"].as_array().unwrap().iter().any(|x| x["title"] == "gearbox.sldasm"
&& x["why"].as_str().unwrap_or("").contains("not created by this bridge")),
s(&r)
);
check!(
b,
"a scratch document that HAS been saved is skipped too",
r["skipped"].as_array().unwrap().iter().any(|x| x["why"].as_str().unwrap_or("").contains("has been saved")),
s(&r)
);
check!(
b,
"our claim on a scratch document SURVIVED its save_as rename",
r["trackedScratchDocuments"].as_array().unwrap().iter()
.any(|t| t.as_str().unwrap_or("").eq_ignore_ascii_case("retargeted.sldprt")),
s(&r)
);
check!(
b,
"the sweep list is reported, so a caller can see what the bridge thinks it owns",
r["trackedScratchDocuments"].is_array(),
s(&r)
);
// ── kill: gated twice, and never actually run here ──────────────────────
// Make a BRIDGE-OWNED document dirty, so the unsaved-work refusal is a real
// branch. (The bridge can no longer dirty the user's documents at all.)
b.call("swx_new_document", json!({"type": "part"}));
let d = b.call("swx_props_set", json!({"properties": {"Scratch": "1"}}));
check!(b, "the kill fixture is dirty", d["doc"]["dirty"] == true, s(&d));
let r = b.call("swx_kill", json!({}));
check!(b, "kill needs a reason", r["errorCode"] == "reason_required", s(&r));
let r = b.call("swx_kill", json!({"reason": "SOLIDWORKS is wedged on a modal nobody can answer"}));
check!(b, "kill needs confirmation too", r["errorCode"] == "confirm_required", s(&r));
// swx_kill ENDS THE USER'S SESSION through ab, so neither veto was ever in
// its path and its own gates were both self-grantable. Choke point now.
let r = b.call("swx_kill", json!({"reason": "wedged", "confirm": true}));
check!(b, "swx_kill needs the process-level opt-in", r["errorCode"] == "user_writes_disabled", s(&r));
// ── close_all_documents: confirm-gated, and it spares unsaved work ──────
let r = b.call("swx_close_all_documents", json!({}));
check!(b, "close_all_documents needs confirmation", r["errorCode"] == "confirm_required", s(&r));
let r = b.call("swx_close_all_documents", json!({"confirm": true, "closeUnsaved": true}));
check!(
b,
"swx_close_all_documents refuses to discard everything without a reason",
r["errorCode"] == "reason_required",
s(&r)
);
// Closing the user's SESSION is a destructive state change in its own right,
// so it needs the process-level opt-in even with confirm and a reason.
let r = b.call("swx_close_all_documents", json!({
"confirm": true, "closeUnsaved": true, "reason": "clearing the bench"
}));
check!(
b,
"swx_close_all_documents needs the process-level opt-in to close the user's session",
r["success"] == false,
s(&r)
);
// With a reason: OUR scratch documents are discarded, the user's dirty ones
// are not — one flag must not throw away everything they have open.
let docs = b.call("swx_docs", json!({}));
check!(
b,
"nothing was closed: the user's documents are all still open",
docs["docs"].as_array().unwrap().len() >= 2,
s(&docs)
);
check!(
b,
"swx_close_all_documents refused without the process-level opt-in",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
// Put the suite's own fixture back for the teardown section below.
b.call("swx_open", json!({"path": "C:\\mock\\bracket.sldprt"}));
// ── escape hatch, state, screenshot ─────────────────────────────────────
let r = b.call("swx_com_eval", json!({"expr": "$sw.RevisionNumber()"}));
check!(b, "com_eval is gated on reason", r["errorCode"] == "reason_required", s(&r));
let r = b.call("swx_com_eval", json!({"expr": "$sw.RevisionNumber()", "reason": "test"}));
check!(b, "com_eval with a reason runs", r["success"] == true, s(&r));
// The mock now runs the SAME grammar the real backend uses, so this proves
// the parser end to end: the chain is reflected back.
check!(b, "com_eval reflects the parsed chain", r["chain"][0] == "RevisionNumber", s(&r));
let r = b.call("swx_com_eval", json!({"reason": "test"}));
check!(b, "com_eval without an expr is rejected", r["success"] == false, s(&r));
// A garbage expression is now REJECTED over the wire (not cheerfully echoed),
// because the mock parses it too — the grammar is genuinely exercised.
let r = b.call("swx_com_eval", json!({"expr": "Remove-Item C:\\", "reason": "test"}));
check!(b, "com_eval rejects a non-expression", r["success"] == false, s(&r));
let r = b.call("swx_state", json!({}));
check!(b, "state", r["success"] == true && !r["openDocs"].is_null(), s(&r));
check!(b, "state etiquette", r["windowEtiquette"] == "background", s(&r));
// ab is unreachable here, so the census is UNKNOWN — and `blockingDialogs`
// must say so rather than hand back an empty array, which a caller reads as
// "nothing is in the way" right before it drives a SOLIDWORKS that may be
// sitting on a modal.
check!(
b,
"state carries a dialog census",
r["blockingDialogs"].is_array() || r["blockingDialogs"].is_null(),
s(&r)
);
// 0.11.1: the census is NATIVE (the process's own windows), so ab being
// unreachable no longer leaves it unknown; where the native census cannot be
// read and ab is the fallback, an unreachable ab is still UNKNOWN.
check!(
b,
"...native and known, or (ab fallback, ab unreachable) UNKNOWN — never an empty 'nothing in the way' it could not see",
(r["dialogCensusSource"] == "native" && r["blockingDialogsKnown"] == true && r["blockingDialogs"].is_array())
|| (r["blockingDialogs"].is_null() && r["blockingDialogsKnown"] == false && r["blockingDialogsError"].is_string()),
s(&r)
);
check!(b, "state carries this machine's measured timings", r["timings"].is_object(), s(&r));
check!(b, "state names the active document", r["activeDocument"].is_string(), s(&r));
let r = b.call("swx_state", json!({"screenshot": false}));
check!(b, "state can skip the ab callbacks", r["screenshot"].is_null(), s(&r));
// ab is not reachable from the test harness: the verb must report that
// cleanly instead of throwing.
let r = b.call("swx_screenshot", json!({}));
check!(
b,
"screenshot reports ab being unreachable instead of blowing up",
r["success"] == false && r["ab"]["error"].is_string(),
s(&r)
);
// ── caller identity reaches the log ─────────────────────────────────────
b.call_with_headers(
"swx_status",
json!({"caller": {"aiThread": "harness-thread", "containerName": "test"}}),
&[("X-Adom-Caller-Thread".into(), "header-thread".into())],
);
check!(
b,
"the caller is logged, one line per verb",
b.log().contains("caller=\"harness-thread\""),
b.log()
);
// ── the safe-close family (Drew's standing authorization, 2026-09-11) ───
let rec = b.call("swx_recover_list", json!({}));
check!(b, "recover_list answers before anything has been preserved", rec["success"] == true, s(&rec));
check!(b, "...with a recovery root", rec["recoveryRoot"].is_string(), s(&rec));
// The user-presence guard's own reading (0.9.9): a simulated SOLIDWORKS is on
// nobody's screen, and the reply says what it measured.
// Unattended sign-in (0.9.13): with no launch waiting at a sign-in window,
// it types nothing anywhere and says so.
// Rendering (0.9.18): a small PNG of the current view, size read back.
let rr = b.call("swx_render_image", json!({"width": 32, "height": 24, "reason": "coverage"}));
check!(b, "render_image writes a PNG of the asked size", rr["success"] == true && rr["width"] == 32 && rr["height"] == 24, s(&rr));
let si = b.call("swx_sign_in", json!({"reason": "coverage"}));
check!(b, "sign_in with nothing waiting: sign_in_not_waiting", si["errorCode"] == "sign_in_not_waiting", s(&si));
let pres = b.call("swx_user_presence", json!({}));
check!(
b,
"user_presence answers, with its evidence",
pres["success"] == true && pres["present"] == false && pres["evidence"]["basis"].is_string(),
s(&pres)
);
// ── teardown verbs ──────────────────────────────────────────────────────
// The bridge closes its OWN documents freely; the user's need the opt-in
// (covered above), so the teardown closes one of ours.
let mine = b.call("swx_new_document", json!({"type": "part"}));
let mine_title = mine["created"]["title"].as_str().unwrap_or("").to_string();
let r = b.call("swx_close_doc", json!({"name": mine_title}));
check!(b, "close doc", r["success"] == true, s(&r));
// Quitting with unsaved work would strand SOLIDWORKS on a save dialog that
// nobody is there to answer, so it refuses until the caller says so.
b.call("swx_new_document", json!({"type": "part"}));
let d = b.call("swx_props_set", json!({"properties": {"Unsaved": "yes"}}));
check!(b, "the exit fixture really is dirty", d["doc"]["dirty"] == true, s(&d));
let r = b.call("swx_stop", json!({}));
// v2 gates on the OPEN-document count FIRST, and that refusal is the
// stronger one: v1 gated on UNSAVED, so five of Drew's CLEAN documents with
// unsavedDocuments:0 would have sailed past the guard. `force` waives the
// unsaved question and never this one.
check!(
b,
"stop refuses while the user's documents are open",
r["errorCode"] == "open_documents_present",
s(&r)
);
check!(b, "...and says force does not waive it", r["_hint"].as_str().unwrap_or("").contains("force` does not"), s(&r));
// Quitting ends the user's SOLIDWORKS session — on a Connected edition the
// bridge cannot start it again — so it needs the process-level opt-in too.
// Checked on an EMPTY session, because the open-document refusal above
// would otherwise answer first and this consent gate would go unproven.
let no_optin = quittable_machine("verb-stop-off", r#"{"a":"exited"}"#);
let off = Bridge::start_with(&[
("SWX_LAUNCH_FAKE_DIR", &no_optin.path()),
("SWX_QUIT_SETTLE_MS", "80"),
]);
off.call("swx_start", json!({}));
let r = off.call("swx_stop", json!({"force": true}));
check!(off, "stop needs the process-level opt-in", r["errorCode"] == "user_writes_disabled", s(&r));
let optin = quittable_machine("verb-stop-on", r#"{"a":"exited"}"#);
let on = Bridge::start_with(&[
("SWX_ALLOW_USER_WRITES", "1"),
("SWX_LAUNCH_FAKE_DIR", &optin.path()),
("SWX_QUIT_SETTLE_MS", "80"),
]);
on.call("swx_start", json!({}));
let r = on.call("swx_stop", json!({
"force": true, "allowUserWrites": true, "confirm": true, "reason": "the user asked me to close SOLIDWORKS"
}));
check!(on, "stop with force", r["success"] == true && r["stopped"] == true, s(&r));
check!(
on,
"stop VERIFIES the app is gone rather than trusting ExitApp's return",
r["stillRunning"] == false,
s(&r)
);
// THE SAFE CLOSE, on this same session — it is teardown, and it is the verb
// Drew's standing authorization exists for: no opt-in, no confirmation, just
// a reason, and every document written to disk before it closes.
let swept = b.call(
"swx_close_all_safely",
json!({"reason": "the user asked me to close SOLIDWORKS"}),
);
check!(
b,
"close_all_safely preserves and closes in the DEFAULT posture",
swept["success"] == true && swept["done"] == true,
s(&swept)
);
check!(
b,
"...and every document it closed was on disk first",
swept["recovered"]
.as_array()
.map(|a| a.iter().all(|d| d["preserved"] == true))
.unwrap_or(false),
s(&swept)
);
let empty = b.call("swx_docs", json!({}));
check!(b, "...leaving the session empty", empty["count"] == 0, s(&empty));
// uninstall removes ONLY the bridge's own exports subtree — never the cache
// root ab owns (which, on a real machine, holds the running exe).
let exports = std::path::Path::new(&b.scratch.path()).join("exports");
check!(
b,
"the exports dir exists before uninstall (the bom csv wrote there)",
exports.is_dir(),
exports.display().to_string()
);
let recovery = std::path::Path::new(&b.scratch.path()).join("recovery");
let rescues_before: Vec<std::path::PathBuf> = std::fs::read_dir(&recovery)
.map(|d| d.flatten().map(|e| e.path()).collect())
.unwrap_or_default();
check!(
b,
"precondition: the sweep above left a recovery folder for uninstall to leave alone",
!rescues_before.is_empty(),
recovery.display().to_string()
);
let r = b.call("swx_uninstall", json!({"cascade": true}));
check!(b, "uninstall", r["success"] == true, s(&r));
check!(b, "uninstall echoes the cascade flag", r["cascade"] == true, s(&r));
// THE RESCUES SURVIVE UNINSTALL. A recovery file can be the only copy of
// work the user had not saved; ab calls this verb on app uninstall, ungated.
check!(
b,
"uninstall PRESERVES every recovery folder, and says so",
rescues_before.iter().all(|p| p.exists()) && r["recoveryPreserved"] == true,
s(&r)
);
// LAST: it ends the session, so nothing may follow it that needs one.
let down = b.call("swx_shutdown", json!({"reason": "the user asked me to close SOLIDWORKS"}));
check!(
b,
"swx_shutdown closes what is open and quits — one verb, no opt-in, only a reason",
down["success"] == true,
s(&down)
);
check!(
b,
"uninstall removed the exports subtree",
!exports.exists(),
s(&r)
);
check!(
b,
"uninstall did NOT delete the cache root (ab owns it)",
std::path::Path::new(&b.scratch.path()).exists(),
b.scratch.path()
);
for name in ["timings.json", "settings.json"] {
let f = std::path::Path::new(&b.scratch.path()).join(name);
check!(
b,
&format!("uninstall removed the bridge's own {name}"),
!f.exists(),
f.display().to_string()
);
}
// The AUDIT LOG survives on purpose. swx_uninstall takes no gate — ab calls
// it on app uninstall — so deleting the ledger here would let a caller erase
// the record of what it did by asking the bridge to tidy up.
let ledger = std::path::Path::new(&b.scratch.path()).join("ledger.jsonl");
check!(
b,
"uninstall PRESERVES swx_action_ledger's log",
ledger.exists(),
ledger.display().to_string()
);
let final_uninstall = b.call("swx_uninstall", json!({}));
check!(b, "...and says so", final_uninstall["ledgerPreserved"] == true, s(&final_uninstall));
let again = b.call("swx_uninstall", json!({}));
check!(b, "uninstall is idempotent", again["success"] == true, s(&again));
// ── the coverage gate ───────────────────────────────────────────────────
// Measured from the calls this suite actually made. A verb added to
// bridge.json (or to swx_describe) without a test turns this red.
let covered = b.covered();
let asserted = b.asserted();
let manifest = manifest_verbs();
let described: std::collections::BTreeSet<String> = described.into_iter().collect();
let untested: Vec<&String> = manifest.difference(&covered).collect();
check!(
b,
"every verb in bridge.json is exercised by this suite",
untested.is_empty(),
format!("untested verbs: {untested:?}")
);
// ...and CALLING a verb is not testing it. Every shipped verb must have at
// least one assertion made against its response, or a verb could ship with a
// one-line POST that proves nothing and still turn the gate green.
let unasserted: Vec<&String> = manifest.difference(&asserted).collect();
check!(
b,
"every verb in bridge.json is ASSERTED on, not merely called",
unasserted.is_empty(),
format!("called but never asserted: {unasserted:?}")
);
let undeclared: Vec<&String> = described.difference(&manifest).collect();
check!(
b,
"swx_describe declares no verb missing from bridge.json",
undeclared.is_empty(),
format!("in describe but not bridge.json: {undeclared:?}")
);
let unimplemented: Vec<&String> = manifest.difference(&described).collect();
check!(
b,
"bridge.json declares no verb missing from swx_describe",
unimplemented.is_empty(),
format!("in bridge.json but not describe: {unimplemented:?}")
);
// The bridge's log must be one line per verb, not a stream of chatter.
let verb_lines = b.log().lines().filter(|l| l.starts_with("[swx] swx_")).count();
check!(
b,
"exactly one log line per verb call — no per-item chatter",
verb_lines == b.call_count(),
format!("{verb_lines} log lines for {} calls", b.call_count())
);
}
#[test]
fn a_handler_panic_becomes_json_and_the_worker_keeps_serving() {
// SWX_TEST_PANIC makes the named verb panic (debug builds only). The HTTP
// worker must catch it, answer success:false + _reportIssues, and stay up —
// not drop the connection or die after WORKERS panics.
let b = Bridge::start_with(&[("SWX_TEST_PANIC", "swx_progress")]);
let r = b.call("swx_progress", json!({}));
check!(b, "a panicking verb still answers success:false", r["success"] == false, r.to_string());
check!(b, "the panic answer invites a bug report", r["_reportIssues"].is_string(), r.to_string());
// Hit it several more times — if the worker had died, we'd eventually stall.
for _ in 0..6 {
let _ = b.call("swx_progress", json!({}));
}
// ...and a DIFFERENT verb still works, proving the process is healthy.
let ok = b.call("swx_status", json!({}));
check!(b, "the bridge still serves other verbs after a panic", ok["success"] == true, ok.to_string());
}
#[test]
fn oversized_and_malformed_bodies_are_rejected_cleanly() {
let b = Bridge::start();
// 400 with a hint, not a 500.
let (code, body) = b.post_raw("{ broken");
check!(b, "malformed body → 400", code == 400, format!("{code} {body}"));
check!(b, "malformed 400 carries a hint", body.contains("_hint"), body.clone());
check!(b, "malformed 400 invites a report", body.contains("_reportIssues"), body.clone());
// Over the 8MB cap → 413.
let huge = format!("{{\"command\":\"swx_status\",\"args\":{{\"x\":\"{}\"}}}}", "a".repeat(9 * 1024 * 1024));
let (code, body) = b.post_raw(&huge);
check!(b, "oversized body → 413", code == 413, format!("{code} {}", &body[..body.len().min(120)]));
}
#[test]
fn a_second_instance_refuses_the_port_instead_of_fighting_for_it() {
let b = Bridge::start();
let out = std::process::Command::new(common::BIN)
.arg("--port")
.arg(b.port.to_string())
.env("SWX_MOCK", "1")
.env("ADOM_BIND_HOST", "127.0.0.1")
.output()
.expect("run second instance");
let text = String::from_utf8_lossy(&out.stdout).to_string();
assert!(out.status.success(), "a duplicate instance must exit 0, got {:?}", out.status);
assert!(text.contains("already in use"), "expected a single-instance notice, got: {text}");
// ...and the original is still serving.
let (code, health) = b.get("/status");
assert_eq!(code, 200, "{health}");
}
#[test]
fn an_edition_that_cannot_be_launched_says_so_instead_of_trying() {
// THE branch that matters on the machine this bridge runs on, and the one
// that was previously unreachable from any test on any platform: swx_start
// short-circuited in mock mode, and the real check needed a Windows box with
// SOLIDWORKS installed. A guard nothing can execute is not a guard.
//
// SWX_TEST_NO_LAUNCH makes `may_launch` answer no, exactly as a Connected
// (or unrecognised) install does. What must happen then is a branchable
// refusal that tells the human what to do — NOT a COM launch, which on a
// Connected edition puts a modal on the user's screen and leaves a dead
// process behind.
let b = Bridge::start_with(&[("SWX_TEST_NO_LAUNCH", "1")]);
let r = b.call("swx_start", json!({}));
check!(b, "start refuses when the edition cannot be launched", r["success"] == false, s(&r));
check!(b, "...with a branchable code", r["errorCode"] == "host_app_launch_blocked", s(&r));
let hint = r["_hint"].as_str().unwrap_or("");
check!(b, "...naming the 3DEXPERIENCE Platform as the way in", hint.contains("3DEXPERIENCE Platform"), hint.to_string());
check!(
b,
"...and telling the AI NOT to launch sldworks.exe itself",
hint.contains("Do not try to launch sldworks.exe by hand"),
hint.to_string()
);
// Nothing was started: the COM host still reports the app as down.
let st = b.call("swx_status", json!({}));
check!(b, "nothing was launched", st["com"]["running"] == false, s(&st));
// ...and once SOLIDWORKS is up by other means, ATTACH still works — which is
// the whole point: the bridge is fully functional on an edition it cannot
// start, it just cannot start it.
let b2 = Bridge::start();
b2.call("swx_start", json!({}));
let b3 = Bridge::start_with(&[("SWX_TEST_NO_LAUNCH", "1")]);
let r = b3.call("swx_readiness", json!({}));
check!(b3, "readiness still answers on a non-launchable edition", r["ready"] == true, s(&r));
}
#[test]
fn a_rename_during_export_is_reported_not_swallowed() {
// Report-bug #2: v0.2 exported with swSaveAsOptions_Silent alone, which
// RENAMES the open document — the user's bracket.sldprt silently became
// bracket.step in memory. The fix is Silent|Copy, and this is the test that
// the fix is actually LOAD-BEARING rather than decorative: with the mock
// injected to rename anyway, the verb must notice and say so, instead of
// reporting a clean export over a document that just lost its identity.
let b = Bridge::start_with(&[("SWX_MOCK_RENAME_ON_EXPORT", "1")]);
b.call("swx_start", json!({}));
b.call("swx_open", json!({"path": "C:\\mock\\bracket.sldprt"}));
let tmp = TempDir::new("rename");
let out = tmp.join("bracket.step");
let r = b.call("swx_export_step", json!({"outputPath": out.to_string_lossy()}));
check!(b, "the export itself succeeded", r["success"] == true, s(&r));
check!(
b,
"a document rename during export is REPORTED, never swallowed",
r["documentRenamed"] == true,
s(&r)
);
// ...and with the fix in place (no injection) it must be false, or the flag
// would be meaningless.
let clean = Bridge::start();
clean.call("swx_start", json!({}));
clean.call("swx_open", json!({"path": "C:\\mock\\bracket.sldprt"}));
let r = clean.call("swx_export_step", json!({"outputPath": tmp.join("clean.step").to_string_lossy()}));
check!(clean, "Silent|Copy leaves the document's identity alone", r["documentRenamed"] == false, s(&r));
check!(clean, "...and the title is genuinely unchanged", r["doc"]["title"] == "bracket.sldprt", s(&r));
}
/// A fake-OS directory in which the quit ladder can actually MEASURE a quit:
/// the mock publishes its session pid there and `FakeOs` synthesises the
/// SOLIDWORKS process, so `swx_stop`'s "the pid and its creation time are gone"
/// test has something to observe. Without one the census is unavailable and the
/// ladder correctly refuses (`stop_preconditions_unknown`) — see tests/quit.rs.
fn quittable_machine(tag: &str, script: &str) -> TempDir {
let d = TempDir::new(tag);
std::fs::write(d.join("quit-script.json"), script).unwrap();
d
}
#[test]
fn ownership_does_not_survive_a_solidworks_restart_or_a_close() {
// Registry entries were keyed on TITLE alone and forgotten only by
// close_doc/reset_workspace. SOLIDWORKS restarts its Part numbering every
// session, so a remembered "Part1" would match a fresh "Part1" the USER
// made — and the bridge would then build geometry into their document
// ungated. It also never forgot a document closed in the SOLIDWORKS UI, by
// a crash, or by CloseAllDocuments.
// Started WITH the opt-in: this test has to stop and restart SOLIDWORKS to
// create a second session, and quitting the user's session is gated.
let machine = quittable_machine("own-quit", r#"{"a":"exited"}"#);
let b = Bridge::start_with(&[
("SWX_ALLOW_USER_WRITES", "1"),
("SWX_LAUNCH_FAKE_DIR", &machine.path()),
("SWX_QUIT_SETTLE_MS", "80"),
]);
b.call("swx_start", json!({}));
let r = b.call("swx_new_document", json!({"type": "part"}));
let title = r["created"]["title"].as_str().unwrap_or("").to_string();
check!(b, "swx_new_document made a scratch part", title.starts_with("Part"), title.clone());
// While it is ours, a mutating verb goes through without confirmation.
let r = b.call("swx_run_script", json!({"script": [
{"op": "sketch_rect", "widthMm": 10, "heightMm": 10},
{"op": "extrude", "depthMm": 2}
]}));
check!(b, "swx_run_script builds into the bridge's own document", r["success"] == true, s(&r));
// Close everything the way SOLIDWORKS itself would — not through
// swx_close_doc. Closing the user's session needs the process-level opt-in,
// so this bridge is started with it to exercise the path.
let r = b.call("swx_close_all_documents", json!({
"confirm": true, "allowUserWrites": true, "reason": "clearing the bench for the test"
}));
check!(b, "swx_close_all_documents runs with the opt-in", r["success"] == true, s(&r));
let r = b.call("swx_reset_workspace", json!({}));
check!(
b,
"the registry no longer claims a document that is not open",
r["trackedScratchDocuments"] == json!([]),
s(&r)
);
// A document the bridge did not create is refused even when it is the only
// thing open.
b.call("swx_open", json!({"path": "C:\\users\\theirs.sldprt"}));
let r = b.call("swx_run_script", json!({"script": [
{"op": "sketch_rect", "widthMm": 10, "heightMm": 10},
{"op": "extrude", "depthMm": 2}
]}));
check!(
b,
"swx_run_script refuses a document the bridge did not create",
r["errorCode"] == "not_bridge_document",
s(&r)
);
// ── and NOTHING carries over a SOLIDWORKS restart ───────────────────────
// This is the case that made title-only ownership dangerous: SOLIDWORKS
// restarts its Part numbering every session, so a remembered "Part1" would
// match a fresh "Part1" the USER made after the restart.
let r = b.call("swx_new_document", json!({"type": "part"}));
let again = r["created"]["title"].as_str().unwrap_or("").to_string();
check!(b, "swx_new_document gives us a scratch document again", !again.is_empty(), again.clone());
let r = b.call("swx_reset_workspace", json!({"titles": ["nothing"]}));
check!(b, "which the bridge tracks", r["trackedScratchDocuments"].as_array().unwrap().contains(&json!(again)), s(&r));
// v2 QUITS ONLY A SESSION WITH NO DOCUMENTS OPEN, and `force` does not waive
// that (v1 gated on the UNSAVED count, which is how five of Drew's clean
// documents could have been quit out from under him). So the sweep comes
// first — which is also what a caller would have to do.
b.call("swx_reset_workspace", json!({"force": true, "allowUserWrites": true, "confirm": true, "reason": "clearing before the restart"}));
b.call("swx_close_all_documents", json!({
"confirm": true, "allowUserWrites": true, "closeUnsaved": true,
"reason": "the test needs an empty session before quitting"
}));
let stopped = b.call("swx_stop", json!({"force": true, "allowUserWrites": true, "confirm": true, "reason": "test needs a second session"}));
check!(b, "with no documents open, the quit succeeds", stopped["stopped"] == true, s(&stopped));
b.call("swx_start", json!({}));
// `keepUnsaved:true` so the document really is untitled-only: the HAZARD this
// test is about is SOLIDWORKS reusing `Part1` across sessions, and from 0.6.0
// a bridge-created document is given a unique file at birth precisely so that
// hazard cannot reach it. The hazard is still real for a document that opts
// out, and the ownership scoping still has to hold for it.
let r = b.call("swx_new_document", json!({"type": "part", "keepUnsaved": true}));
let after_restart = r["created"]["title"].as_str().unwrap_or("").to_string();
check!(
b,
"a new SOLIDWORKS session REUSES the title Part1, exactly as the real one does",
after_restart == "Part1",
format!("{after_restart} {}", s(&r))
);
let r = b.call("swx_reset_workspace", json!({"titles": ["nothing"]}));
let tracked = r["trackedScratchDocuments"].as_array().cloned().unwrap_or_default();
check!(
b,
"swx_reset_workspace tracks ONLY the new session's document, not the old one with the same title",
tracked.len() == 1 && tracked[0] == json!("Part1"),
s(&r)
);
}
#[test]
fn two_open_documents_sharing_a_title_cannot_be_confused() {
// THE round-3 hole, and the reason "ownership by title" was never sound.
// `C:\users\thing.sldprt` and `C:\bridge\thing.sldprt` are both titled
// `thing.sldprt`; having two such documents open is ordinary. Ownership was
// (title, session) and GetOpenDocumentByName returns whichever it finds
// first, so `swx_save_as {targetDocument:"thing.sldprt"}` passed the gate
// against the BRIDGE's copy and renamed and relocated the USER's — with no
// confirm and no reason. The refusal even handed the AI the title to retry
// with.
let b = Bridge::start();
b.call("swx_start", json!({}));
// Hermetic directories: the overwrite guard is real, so a leftover file from
// a previous run would (correctly) refuse the setup.
let theirs = TempDir::new("theirs");
let ours = TempDir::new("ours");
// NOTE the extension case: a non-copy save_as reports the path back with an
// UPPERCASED extension, so the user's document has to be opened the same way
// for the titles to genuinely collide — which is the situation under test.
let their_path = theirs.join("thing.SLDPRT").to_string_lossy().to_string();
let our_path = ours.join("thing.sldprt").to_string_lossy().to_string();
// The user's document, with unsaved work in it.
b.call("swx_open", json!({"path": their_path.clone()}));
// NOTE: the bridge can no longer make their document dirty at all —
// swx_props_set is gated now — which is itself the improvement. What this
// test proves is that a colliding title cannot make a write land on it.
// The bridge's own document, saved under the SAME title in another folder.
b.call("swx_new_document", json!({"type": "part"}));
let mine = b.call("swx_save_as", json!({"outputPath": our_path.clone()}));
check!(b, "the bridge has its own same-titled document", mine["success"] == true, s(&mine));
let docs = b.call("swx_docs", json!({}));
let same_titled = docs["docs"]
.as_array()
.unwrap()
.iter()
.filter(|d| d["title"].as_str().unwrap_or("").eq_ignore_ascii_case("thing.sldprt"))
.count();
check!(b, "two open documents really do share the title", same_titled == 2, s(&docs));
// (a) save_as by that title must REFUSE, not pick one.
let r = b.call("swx_save_as", json!({
"targetDocument": "thing.SLDPRT",
"outputPath": ours.join("renamed.sldprt").to_string_lossy()
}));
check!(b, "swx_save_as refuses an ambiguous title", r["errorCode"] == "ambiguous_target", s(&r));
check!(
b,
"...and lists the candidates with their paths, so the caller can disambiguate",
r["candidates"].as_array().map(|c| c.len()) == Some(2),
s(&r)
);
// The user's document must be IDENTICAL — same title AND same path. "Still
// open" is not the assertion: in the bug it WAS still open, under a new name
// at a new path inside the bridge's folder.
let docs = b.call("swx_docs", json!({}));
let user_doc = docs["docs"]
.as_array()
.unwrap()
.iter()
.find(|d| d["path"] == json!(their_path.clone()))
.cloned();
check!(b, "the user's document has its EXACT title and path", user_doc.is_some(), s(&docs));
check!(
b,
"...and is unmoved: the path is exactly the one it was opened from",
user_doc.as_ref().map(|d| d["path"] == json!(their_path.clone())).unwrap_or(false),
s(&docs)
);
// (b) close_doc by that title must refuse too — it was the one destructive
// verb that never resolved a target at all.
let r = b.call("swx_close_doc", json!({"name": "thing.SLDPRT", "save": false}));
check!(b, "swx_close_doc refuses an ambiguous title", r["errorCode"] == "ambiguous_target", s(&r));
let docs = b.call("swx_docs", json!({}));
check!(
b,
"...and closed nothing: BOTH documents are still open",
docs["docs"].as_array().unwrap().iter().filter(|d| d["title"].as_str().unwrap_or("").eq_ignore_ascii_case("thing.sldprt")).count() == 2,
s(&docs)
);
// Naming the user's copy unambiguously by PATH still protects their work.
let r = b.call("swx_close_doc", json!({"path": their_path.clone(), "save": false}));
check!(
b,
"swx_close_doc refuses to close a document the bridge did not create",
r["success"] == false,
s(&r)
);
// (c) the bridge's OWN copy, named by path, is fair game and closes cleanly.
let r = b.call("swx_close_doc", json!({"path": our_path.clone(), "save": false}));
check!(b, "the bridge's own same-titled document closes without ceremony", r["success"] == true, s(&r));
let docs = b.call("swx_docs", json!({}));
check!(
b,
"and the user's document is STILL exactly where it was",
docs["docs"].as_array().unwrap().iter().any(|d| d["path"] == json!(their_path.clone())
&& d["title"].as_str().unwrap_or("").eq_ignore_ascii_case("thing.sldprt")),
s(&docs)
);
}
#[test]
fn writes_are_deny_by_default_and_the_opt_in_is_not_self_grantable() {
// THE design change, after five rounds in which every Critical was the
// bridge writing to something the user owns through a route whose guard had
// been forgotten. Writing is now refused unless BOTH the document is ours
// AND the destination is new; the exception needs a process-level opt-in an
// AI cannot give itself.
let off = Bridge::start();
off.call("swx_start", json!({}));
let st = off.call("swx_status", json!({}));
check!(off, "swx_status reports the write posture", st["userWrites"] == "disabled", s(&st));
let d = off.call("swx_describe", json!({}));
check!(off, "swx_describe states the write policy", d["writePolicy"].as_str().unwrap_or("").contains("DENY BY DEFAULT"), s(&d));
let tmp = TempDir::new("deny");
off.call("swx_open", json!({"path": "C:\\users\\theirs.sldprt"}));
// Every route that reaches the user's document is refused, by ONE code.
for (verb, args) in [
("swx_save", json!({})),
("swx_close_doc", json!({"save": true})),
("swx_set_parameter", json!({"name": "D1@Boss-Extrude1", "valueMm": 5})),
("swx_set_preference", json!({"name": "lengthUnitRaw", "value": 1})),
("swx_run_script", json!({"script": [{"op": "rebuild"}]})),
("swx_save_as", json!({"outputPath": tmp.join("x.sldprt").to_string_lossy()})),
] {
let r = off.call(verb, args);
check!(
off,
&format!("{verb} is denied by default on the user's document"),
r["errorCode"] == "user_writes_disabled",
s(&r)
);
check!(off, &format!("{verb} names the opt-in it would need"), r["requires"]["env"] == "SWX_ALLOW_USER_WRITES=1", s(&r));
}
// ...and an existing destination is refused even for OUR OWN document —
// ownership of the source says nothing about the file being clobbered.
off.call("swx_new_document", json!({"type": "part"}));
let occupied = tmp.join("already.step");
std::fs::write(&occupied, b"the user's existing export").expect("fixture");
for (verb, args) in [
("swx_export", json!({"format": "step", "outputPath": occupied.to_string_lossy()})),
("swx_save_as", json!({"outputPath": occupied.to_string_lossy()})),
("swx_run_script", json!({"script": [{"op": "save_as", "path": occupied.to_string_lossy()}]})),
] {
let r = off.call(verb, args);
check!(
off,
&format!("{verb} refuses to overwrite an existing file"),
r["errorCode"] == "destination_exists",
s(&r)
);
}
check!(
off,
"and the existing file was NOT touched",
std::fs::read(&occupied).unwrap() == b"the user's existing export",
"bytes changed"
);
// A NEW destination needs no permission at all — the safe subset works.
let fresh = tmp.join("fresh.step");
let r = off.call("swx_export", json!({"format": "step", "outputPath": fresh.to_string_lossy()}));
check!(off, "exporting to a NEW path needs no permission", r["success"] == true && fresh.is_file(), s(&r));
// ── the enabled posture ─────────────────────────────────────────────────
let on = Bridge::start_with(&[("SWX_ALLOW_USER_WRITES", "1")]);
on.call("swx_start", json!({}));
let st = on.call("swx_status", json!({}));
check!(on, "the enabled posture is reported too", st["userWrites"] == "enabled", s(&st));
on.call("swx_open", json!({"path": "C:\\users\\theirs.sldprt"}));
// Even enabled, the call must ASK — the env is permission to ask, not a
// blanket grant.
let r = on.call("swx_save", json!({}));
check!(on, "with the env on, a call that does not ask is still refused", r["errorCode"] == "not_bridge_document", s(&r));
let r = on.call("swx_save", json!({"confirm": true, "reason": "x"}));
check!(on, "...and confirm+reason without allowUserWrites is not asking", r["errorCode"] == "not_bridge_document", s(&r));
let r = on.call("swx_save", json!({
"allowUserWrites": true, "confirm": true,
"reason": "the user asked me to save their part"
}));
check!(on, "all three, with the env on, goes through", r["success"] == true, s(&r));
let led = on.call("swx_action_ledger", json!({"limit": 10}));
check!(
on,
"and every granted exception is recorded with the caller's reason",
led["actions"].as_array().unwrap().iter().any(|a| a["action"] == "user_write_authorized"
&& a["detail"]["reason"] == "the user asked me to save their part"),
s(&led)
);
}
#[test]
fn the_com_host_refuses_a_write_the_server_would_have_allowed() {
// Rounds 3 and 5 both turned on the server-authorizes / host-acts split: a
// server-side check is a CLAIM, and the host acted on it. The host now keeps
// its own record of the documents it created and reads the opt-in from the
// environment it inherited, so it can veto independently.
//
// Driven over the RAW stdio protocol, bypassing the server entirely — which
// is precisely the attack the split allowed.
use std::io::Write;
// ISOLATED scratch: the com host PERSISTS its created-document record, so a
// test sharing the developer's real scratch root inherits whatever ran
// before it and passes or fails by accident.
let scratch = TempDir::new("hostveto");
let mut child = std::process::Command::new(common::BIN)
.arg("--com-host")
.env("SWX_MOCK", "1")
.env("SWX_SCRATCH_DIR", scratch.path())
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.expect("spawn com host");
let mut stdin = child.stdin.take().unwrap();
let mut stdout = std::io::BufReader::new(child.stdout.take().unwrap());
fn read(stdout: &mut std::io::BufReader<std::process::ChildStdout>) -> Value {
loop {
let mut line = String::new();
std::io::BufRead::read_line(stdout, &mut line).expect("read");
let v: Value = serde_json::from_str(line.trim()).unwrap_or(Value::Null);
if v.get("event").is_none() && !v.is_null() {
return v;
}
}
}
{
// consume the `ready` event
let mut line = String::new();
std::io::BufRead::read_line(&mut stdout, &mut line).expect("ready");
}
writeln!(stdin, r#"{{"id":1,"cmd":"start","args":{{"launch":true}}}}"#).unwrap();
let _ = read(&mut stdout);
writeln!(stdin, r#"{{"id":2,"cmd":"open","args":{{"path":"C:\\users\\theirs.sldprt","launch":true}}}}"#).unwrap();
let _ = read(&mut stdout);
// The server would have refused this; here we forge the authorization the
// server alone would have granted. The host must still say no, because the
// document is not one IT created and the environment opt-in is absent.
writeln!(
stdin,
r#"{{"id":3,"cmd":"save","args":{{"path":"C:\\users\\theirs.sldprt","allowUserWrites":true}}}}"#
)
.unwrap();
let r = read(&mut stdout);
assert_eq!(r["ok"], false, "the com host must veto independently: {r}");
let err = r["error"].as_str().unwrap_or("");
assert!(err.contains("com host refuses"), "{err}");
assert!(
r["detail"].as_str().unwrap_or("").contains("OWN check"),
"the refusal must say it is the host's own decision: {r}"
);
// A document the HOST itself created is fine, with no forged flag at all.
writeln!(stdin, r#"{{"id":4,"cmd":"new_document","args":{{"type":"part"}}}}"#).unwrap();
let made = read(&mut stdout);
assert_eq!(made["ok"], true, "{made}");
let title = made["result"]["created"]["title"].as_str().unwrap().to_string();
writeln!(stdin, r#"{{"id":5,"cmd":"save","args":{{"docName":"{title}"}}}}"#).unwrap();
let r = read(&mut stdout);
assert_eq!(r["ok"], true, "the host's own document saves without ceremony: {r}");
writeln!(stdin, r#"{{"id":0,"cmd":"__exit__"}}"#).unwrap();
let _ = child.wait();
}
#[test]
fn every_write_route_is_covered_by_the_choke_point() {
// Round 6: `authorize` was wired into 7 verbs and three others wrote around
// it. One test per route, so a route that loses its guard fails HERE rather
// than in the next review.
let b = Bridge::start();
b.call("swx_start", json!({}));
b.call("swx_open", json!({"path": "C:\\users\\gearbox.sldasm"}));
let tmp = TempDir::new("routes");
// C1: the BOM CSV is written by the SERVER with std::fs::write, so neither
// the com host's veto nor safe_out stood between it and any file on disk.
let victim = tmp.join("quarterly-report.csv");
std::fs::write(&victim, b"THE USER'S QUARTERLY REPORT").expect("fixture");
let r = b.call("swx_bom", json!({"format": "csv", "out": victim.to_string_lossy()}));
check!(b, "swx_bom refuses to overwrite an existing file", r["errorCode"] == "destination_exists", s(&r));
check!(
b,
"...and the user's file is byte-for-byte unchanged",
std::fs::read(&victim).unwrap() == b"THE USER'S QUARTERLY REPORT",
"the CSV overwrote it"
);
// A fresh destination is the normal case and needs no permission.
let fresh = tmp.join("bom.csv");
let r = b.call("swx_bom", json!({"format": "csv", "out": fresh.to_string_lossy()}));
check!(b, "swx_bom writes to a new path", r["success"] == true && fresh.is_file(), s(&r));
// C2: props_set leaves the document DIRTY, so the user's own Ctrl+S commits
// bridge-authored data. "In memory until a gated save" ignored the persist
// route that cannot be gated: the user.
let r = b.call("swx_props_set", json!({"properties": {"Injected": "yes"}}));
check!(b, "swx_props_set refuses to modify the user's document", r["errorCode"] == "user_writes_disabled", s(&r));
let after = b.call("swx_document_info", json!({}));
check!(b, "...and left it clean, so SOLIDWORKS will not prompt to save", after["dirty"] == false, s(&after));
// C3: rebuild_check calls ForceRebuild3 — which this bridge's own com_eval
// hazard list refuses as "rebuilds the user's model".
let r = b.call("swx_rebuild_check", json!({}));
check!(b, "swx_rebuild_check refuses to rebuild the user's document", r["errorCode"] == "user_writes_disabled", s(&r));
// M4: the server asked "is the directory non-empty?" while the host asked
// `exists()`, so a bridge-owned assembly into an existing EMPTY directory
// was granted by one and refused by the other — fail-closed, but the verb
// was dead for its most natural case and the two guards were answering
// different questions.
let empty_dir = tmp.join("already-there");
std::fs::create_dir_all(&empty_dir).expect("fixture");
b.call("swx_new_document", json!({"type": "assembly"}));
let r = b.call("swx_pack_and_go", json!({"out": empty_dir.to_string_lossy()}));
check!(
b,
"swx_pack_and_go: server and host agree that an EXISTING destination is refused",
r["errorCode"] == "destination_exists",
s(&r)
);
b.call("swx_close_doc", json!({"save": false}));
// M1: pack_and_go must not leave a directory behind after refusing.
let png = tmp.join("packandgo-refused");
let r = b.call("swx_pack_and_go", json!({"out": png.to_string_lossy()}));
check!(b, "swx_pack_and_go refuses the user's document", r["errorCode"] == "user_writes_disabled", s(&r));
check!(b, "...and created no directory", !png.exists(), png.display().to_string());
}
#[test]
fn the_host_authorizes_every_mutating_command_not_a_remembered_list() {
// H1: the host's veto covered 7 commands. A forged `save` was vetoed while a
// forged `run_script` — the whole modelling DSL — sketched and extruded into
// the user's open document. The wrapper is now on the DISPATCH LOOP and
// keyed off a READ-ONLY list, so a command nobody classified is refused.
use std::io::Write;
let scratch = TempDir::new("hostdispatch");
let mut child = std::process::Command::new(common::BIN)
.arg("--com-host")
.env("SWX_MOCK", "1")
.env("SWX_SCRATCH_DIR", scratch.path())
.env_remove("SWX_ALLOW_USER_WRITES")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.expect("spawn com host");
let mut stdin = child.stdin.take().unwrap();
let mut stdout = std::io::BufReader::new(child.stdout.take().unwrap());
fn read(stdout: &mut std::io::BufReader<std::process::ChildStdout>) -> Value {
loop {
let mut line = String::new();
std::io::BufRead::read_line(stdout, &mut line).expect("read");
let v: Value = serde_json::from_str(line.trim()).unwrap_or(Value::Null);
if v.get("event").is_none() && !v.is_null() {
return v;
}
}
}
{
let mut line = String::new();
std::io::BufRead::read_line(&mut stdout, &mut line).expect("ready");
}
writeln!(stdin, r#"{{"id":1,"cmd":"start","args":{{"launch":true}}}}"#).unwrap();
let _ = read(&mut stdout);
writeln!(stdin, r#"{{"id":2,"cmd":"open","args":{{"path":"C:\\users\\theirs.sldprt","launch":true}}}}"#).unwrap();
let _ = read(&mut stdout);
// THE forged request that used to work: a DSL script with no targetDocument,
// which the host resolved to the user's ActiveDoc and ran.
writeln!(
stdin,
r#"{{"id":3,"cmd":"run_script","args":{{"allowUserWrites":true,"script":[{{"op":"sketch_rect","widthMm":10,"heightMm":10}},{{"op":"extrude","depthMm":2}}]}}}}"#
)
.unwrap();
let r = read(&mut stdout);
assert_eq!(r["ok"], false, "a forged run_script must be vetoed by the host: {r}");
// ...and the user's document is untouched.
writeln!(stdin, r#"{{"id":4,"cmd":"inspect_bodies","args":{{"path":"C:\\users\\theirs.sldprt"}}}}"#).unwrap();
let bodies = read(&mut stdout);
assert_eq!(bodies["result"]["bodyCount"], 1, "no body was added: {bodies}");
// Every other command that used to be missing from the veto.
for (cmd, args) in [
("props_set", r#"{"allowUserWrites":true,"properties":{"x":"1"}}"#),
("rebuild_check", r#"{"allowUserWrites":true}"#),
("close_doc", r#"{"allowUserWrites":true,"save":false}"#),
("close_all_documents", r#"{"allowUserWrites":true}"#),
("stop", r#"{"allowUserWrites":true}"#),
] {
writeln!(stdin, r#"{{"id":9,"cmd":"{cmd}","args":{args}}}"#).unwrap();
let r = read(&mut stdout);
assert_eq!(r["ok"], false, "forged {cmd} must be vetoed: {r}");
assert!(
r["error"].as_str().unwrap_or("").contains("com host refuses"),
"{cmd}: the refusal must come from the HOST: {r}"
);
}
// H2: `eval` can reach ANY COM member and had no host-side guard at all.
writeln!(
stdin,
r#"{{"id":20,"cmd":"eval","args":{{"allowUserWrites":true,"expr":"$doc.SaveAs3(\"C:\\x.step\",0,1)"}}}}"#
)
.unwrap();
let r = read(&mut stdout);
assert_eq!(r["ok"], false, "the host must re-check the read allowlist: {r}");
assert!(r["error"].as_str().unwrap_or("").contains("refuses to evaluate"), "{r}");
// A read still goes through.
writeln!(stdin, r#"{{"id":21,"cmd":"eval","args":{{"expr":"$sw.RevisionNumber()"}}}}"#).unwrap();
let r = read(&mut stdout);
assert_eq!(r["ok"], true, "reads are what the escape hatch is for: {r}");
writeln!(stdin, r#"{{"id":0,"cmd":"__exit__"}}"#).unwrap();
let _ = child.wait();
}
#[test]
fn the_ab_delegated_verbs_end_the_session_and_are_gated_like_the_com_ones() {
// Round 7: the half of the bridge that never speaks to the com host. Two
// verbs end the user's SOLIDWORKS session through ab, so NEITHER of the two
// vetoes was ever in their path, and their own gates (confirm + reason) are
// both things a caller writes for itself.
let off = Bridge::start();
off.call("swx_start", json!({}));
// C1: force-kill was dispatchable in the DEFAULT posture.
let r = off.call("swx_kill", json!({"reason": "wedged", "confirm": true, "force": true}));
check!(off, "swx_kill is denied by default", r["errorCode"] == "user_writes_disabled", s(&r));
let led = off.call("swx_action_ledger", json!({"limit": 20}));
check!(
off,
"...and nothing was dispatched: no kill in the ledger",
!led["actions"].as_array().unwrap().iter().any(|a| a["action"] == "solidworks_killed"),
s(&led)
);
// With the opt-in ON, an unsaved BRIDGE document still stops it — and the
// fail-closed reading of an UNANSWERABLE probe is unit-tested in
// verbs3::kill_tests, which is where a mock cannot help.
let on = Bridge::start_with(&[("SWX_ALLOW_USER_WRITES", "1")]);
on.call("swx_start", json!({}));
on.call("swx_new_document", json!({"type": "part"}));
on.call("swx_props_set", json!({"properties": {"x": "1"}}));
let r = on.call("swx_kill", json!({
"reason": "wedged", "confirm": true, "allowUserWrites": true
}));
check!(
on,
"swx_kill still refuses while documents have unsaved changes",
r["errorCode"] == "unsaved_documents",
s(&r)
);
// C2: the application FRAME is not a dialog.
let st = off.call("swx_status", json!({}));
check!(off, "the bridge is in the default posture for this check", st["userWrites"] == "disabled", s(&st));
// swx_window_info marks frames, and the mock's mainHwnd is 4242.
let r = off.call("swx_close_window", json!({
"hwnd": 4242, "reason": "tidying up", "confirm": true
}));
// Pinned to the FRAME code specifically. Accepting `window_not_attributed`
// as an alternative meant this assertion passed for the wrong reason (ab is
// unreachable in this bridge) and pinned nothing.
check!(
off,
"swx_close_window refuses the SOLIDWORKS application frame, by frame",
r["errorCode"] == "window_is_application_frame",
s(&r)
);
check!(
off,
"...and never reports success on it",
r["success"] == false,
s(&r)
);
}
#[test]
fn the_ab_delegated_half_is_gated_and_dispatches_nothing_when_refused() {
// The half of the bridge that never speaks to the com host. Until now these
// verbs only ever ran their "ab is unreachable" branch in tests, so a guard
// on this side could be deleted with every test still green — which is how
// swx_kill force-killing SOLIDWORKS in the default posture survived six
// reviews. The stub makes the ab side real: it reports windows WITH pids, so
// attribution actually succeeds, and it records what was dispatched.
let ab = common::AbStub::start(json!([]));
let b = Bridge::start_with(&[("ADOM_DIRECT_API_URL", &ab.url)]);
b.call("swx_start", json!({}));
b.call("swx_open", json!({"path": "C:\\users\\gearbox.sldasm"}));
// Attribution matches on OWNER PID, so the stub's windows have to carry the
// pid SOLIDWORKS is actually reporting — which is only knowable now.
let state = b.call("swx_get_app_state", json!({}));
let sw_pid = state["processId"].as_i64().expect("the mock reports a process id");
let main = state["mainHwnd"].as_i64().expect("and a main window handle");
ab.set_windows(json!([
{"hwnd": main, "title": "SOLIDWORKS Design Professional for Makers 2026",
"className": "Afx:0000000140000000", "pid": sw_pid, "rect": {}},
{"hwnd": 858186, "title": "Export", "className": "#32770", "pid": sw_pid, "rect": {}},
{"hwnd": 9001, "title": "Save changes to gearbox?", "className": "#32770", "pid": sw_pid, "rect": {}},
{"hwnd": 9002, "title": "System Options", "className": "#32770", "pid": sw_pid, "rect": {}}
]));
// The mock's mainHwnd is 4242 and the stub reports that pid, so windows are
// genuinely attributable here — the condition under which the guard matters.
let wi = b.call("swx_window_info", json!({}));
let frame = wi["windows"].as_array().unwrap().iter().find(|w| w["hwnd"] == main).cloned();
check!(b, "the stub's windows are attributable", frame.is_some(), s(&wi));
let frame = frame.unwrap();
check!(b, "the main window is ownerConfirmed", frame["ownerConfirmed"] == true, s(&frame));
check!(b, "...and is marked as a FRAME", frame["isFrame"] == true, s(&frame));
check!(b, "...and therefore NOT closable", frame["closable"] == false, s(&frame));
// C2: closing the frame quits SOLIDWORKS. `ownerConfirmed` is true exactly
// when a window is most provably ours, so the attribution guard PASSED on
// the one window it must never pass on.
let r = b.call("swx_close_window", json!({"hwnd": main, "reason": "tidying", "confirm": true}));
check!(b, "swx_close_window refuses the application frame", r["errorCode"] == "window_is_application_frame", s(&r));
// A SECOND SOLIDWORKS frame — a separate document window — is not the one
// COM told us about, so it takes the attribution path rather than the
// main-hwnd shortcut. It is still a frame, and closing it still quits.
ab.set_windows(json!([
{"hwnd": main, "title": "SOLIDWORKS", "className": "Afx:0000000140000000", "pid": sw_pid, "rect": {}},
{"hwnd": 5555, "title": "SOLIDWORKS Premium 2026 - [bracket.sldprt]",
"className": "Afx:0000000140000001", "pid": sw_pid, "rect": {}},
{"hwnd": 858186, "title": "Export", "className": "#32770", "pid": sw_pid, "rect": {}},
{"hwnd": 9001, "title": "Save changes to gearbox?", "className": "#32770", "pid": sw_pid, "rect": {}},
{"hwnd": 9002, "title": "System Options", "className": "#32770", "pid": sw_pid, "rect": {}}
]));
let r = b.call("swx_close_window", json!({"hwnd": 5555, "reason": "tidying", "confirm": true}));
check!(
b,
"swx_close_window refuses a SECOND SOLIDWORKS frame too, not just the main hwnd",
r["errorCode"] == "window_is_application_frame",
s(&r)
);
// C1: force-kill, with both of its old gates satisfied.
let r = b.call("swx_kill", json!({"reason": "self-granted", "confirm": true, "force": true}));
check!(b, "swx_kill is refused in the default posture", r["errorCode"] == "user_writes_disabled", s(&r));
// H1: with attribution working, a dialog the bridge did NOT raise becomes
// closable unless the opt-in is present. `Export` is ours; `System Options`
// and `Save changes?` are the user's.
let r = b.call("swx_dismiss_blocking_dialogs", json!({
"close": true, "confirm": true, "reason": "tidying up"
}));
let refused: Vec<String> = r["refused"]
.as_array()
.unwrap()
.iter()
.map(|x| x["title"].as_str().unwrap_or("").to_string())
.collect();
check!(
b,
"a 'settings' dialog the user may be answering is NOT closed without the opt-in",
refused.iter().any(|t| t == "System Options"),
s(&r)
);
check!(
b,
"and neither is the bridge's OWN export sheet — closing any modal can take SOLIDWORKS down",
refused.iter().any(|t| t == "Export"),
s(&r)
);
check!(
b,
"a Save-changes dialog is never closed at all",
refused.iter().any(|t| t.starts_with("Save changes")),
s(&r)
);
// THE assertion that matters: nothing destructive was DISPATCHED. A refusal
// that returns an error while still sending the request is not a refusal.
check!(
b,
"NOTHING destructive reached ab",
ab.destructive_calls().is_empty(),
format!("{:?}", ab.destructive_calls())
);
check!(b, "...though ab was genuinely reachable", !ab.calls_to("desktop_list_windows").is_empty(), "no window calls");
// With the opt-in, the bridge's OWN export sheet is closable — the narrow
// case this verb exists for.
// The export sheet's WORDS are read before it is closed (0.9.7, F6: one
// classifier with judge_window), so the scenario supplies them.
let words = TempDir::new("dismiss-words");
std::fs::write(words.join("windows.json"), "[]").unwrap();
std::fs::write(
words.join("dialog-open.json"),
json!({"hwnd": 858186, "text": ["Export options"], "buttons": ["OK", "Cancel"]}).to_string(),
)
.unwrap();
let on = Bridge::start_with(&[
("ADOM_DIRECT_API_URL", &ab.url),
("SWX_ALLOW_USER_WRITES", "1"),
("SWX_LAUNCH_FAKE_DIR", &words.path()),
]);
on.call("swx_start", json!({}));
let on_state = on.call("swx_get_app_state", json!({}));
let on_pid = on_state["processId"].as_i64().unwrap_or(sw_pid);
ab.set_windows(json!([
{"hwnd": on_state["mainHwnd"].as_i64().unwrap_or(main), "title": "SOLIDWORKS",
"className": "Afx:0000000140000000", "pid": on_pid, "rect": {}},
{"hwnd": 858186, "title": "Export", "className": "#32770", "pid": on_pid, "rect": {}},
{"hwnd": 9001, "title": "Save changes to gearbox?", "className": "#32770", "pid": on_pid, "rect": {}}
]));
let r = on.call("swx_dismiss_blocking_dialogs", json!({
"close": true, "confirm": true, "allowUserWrites": true,
"reason": "clearing the export sheet my own SaveAs3 raised"
}));
let closed: Vec<String> = r["closed"]
.as_array()
.unwrap()
.iter()
.map(|x| x["title"].as_str().unwrap_or("").to_string())
.collect();
check!(on, "the bridge's own export sheet closes with the opt-in", closed.iter().any(|t| t == "Export"), s(&r));
check!(
on,
"...and the user's Save-changes dialog STILL is not touched",
!closed.iter().any(|t| t.starts_with("Save changes")),
s(&r)
);
}
/// ONE CLASSIFIER (0.9.7, adversary pass 44 F6): swx_dismiss_blocking_dialogs
/// reads a window's WORDS with the classifier judge_window uses before it
/// closes anything. A window titled "Warning" (kind `message`, answerable by
/// title) whose body is a save question is NOT closed under the opt-in; a
/// window whose words cannot be read is not closed either.
#[test]
fn a_save_question_under_a_warning_title_is_never_dismissed() {
let ab = common::AbStub::start(json!([]));
let words = TempDir::new("dismiss-warning-words");
std::fs::write(words.join("windows.json"), "[]").unwrap();
std::fs::write(
words.join("dialog-open.json"),
json!({"hwnd": 858187, "text": ["Do you want to save changes to Part1.SLDPRT?"],
"buttons": ["Save", "Don't Save", "Cancel"]}).to_string(),
)
.unwrap();
let on = Bridge::start_with(&[
("ADOM_DIRECT_API_URL", &ab.url),
("SWX_ALLOW_USER_WRITES", "1"),
("SWX_LAUNCH_FAKE_DIR", &words.path()),
]);
on.call("swx_start", json!({}));
let st = on.call("swx_get_app_state", json!({}));
let pid = st["processId"].as_i64().unwrap();
ab.set_windows(json!([
{"hwnd": st["mainHwnd"].as_i64().unwrap(), "title": "SOLIDWORKS", "className": "Afx:0001", "pid": pid, "rect": {}},
{"hwnd": 858187, "title": "Warning", "className": "#32770", "pid": pid, "rect": {}},
{"hwnd": 858188, "title": "Rebuild Errors", "className": "#32770", "pid": pid, "rect": {}}
]));
let r = on.call("swx_dismiss_blocking_dialogs", json!({
"close": true, "confirm": true, "allowUserWrites": true, "reason": "tidying"
}));
check!(on, "nothing was closed", r["closed"].as_array().map(|c| c.is_empty()).unwrap_or(false), s(&r));
let refused = r["refused"].as_array().cloned().unwrap_or_default();
check!(
on,
"the 'Warning' window is refused BY ITS TEXT as a save question",
refused.iter().any(|x| x["hwnd"] == 858187 && x["kind"] == "save_changes"
&& x["why"].as_str().unwrap_or("").contains("by its text")),
s(&r)
);
check!(
on,
"a window whose words cannot be read is refused, not closed",
refused.iter().any(|x| x["hwnd"] == 858188 && x["why"].as_str().unwrap_or("").contains("could not be read")),
s(&r)
);
check!(on, "NOTHING destructive reached ab", ab.destructive_calls().is_empty(), format!("{:?}", ab.destructive_calls()));
}
#[test]
fn a_host_record_from_another_solidworks_session_is_not_evidence() {
// M1: the persisted record carried no session id and was cleared only on a
// clean `stop`. After a crash, a kill, or the user quitting SOLIDWORKS, it
// survived — and their next `Part1` matched it. The server's record is
// session-scoped so the write was still refused end to end, but a second
// veto that can be wrong is not a second veto.
let b = Bridge::start();
let store = std::path::Path::new(&b.scratch.path()).join("com-host-created.json");
std::fs::create_dir_all(&b.scratch.path()).ok();
std::fs::write(
&store,
json!({"session": 111111, "documents": [{"title": "Part1", "path": null}]}).to_string(),
)
.expect("plant a stale record");
b.call("swx_start", json!({}));
// The USER's Part1 — same title, different SOLIDWORKS session.
b.call("swx_open", json!({"path": "C:\\users\\Part1.sldprt"}));
let r = b.call("swx_props_set", json!({"targetDocument": "Part1.sldprt", "properties": {"x": "1"}}));
check!(
b,
"a record from another SOLIDWORKS session confers no ownership",
r["success"] == false,
s(&r)
);
}
#[test]
fn closing_a_window_is_gated_the_same_way_its_sibling_is() {
// ROUND 8's Critical. `swx_close_window` had neither the `neverAutoAnswer`
// refusal nor the write opt-in, while `swx_dismiss_blocking_dialogs` — the
// sibling operating on the SAME window list — had both. And
// `swx_window_info` advertised the dialog as `closable:true`, the same
// discoverability shape as round 7's `mainHwnd`.
let ab = common::AbStub::start(json!([]));
let b = Bridge::start_with(&[("ADOM_DIRECT_API_URL", &ab.url)]);
b.call("swx_start", json!({}));
let state = b.call("swx_get_app_state", json!({}));
let sw_pid = state["processId"].as_i64().unwrap();
let main = state["mainHwnd"].as_i64().unwrap();
ab.set_windows(json!([
{"hwnd": main, "title": "SOLIDWORKS", "className": "Afx:0001", "pid": sw_pid, "rect": {}},
{"hwnd": 9001, "title": "Save changes to gearbox?", "className": "#32770", "pid": sw_pid, "rect": {}},
{"hwnd": 9002, "title": "SOLIDWORKS Document Recovery", "className": "#32770", "pid": sw_pid, "rect": {}},
{"hwnd": 9003, "title": "Export", "className": "#32770", "pid": sw_pid, "rect": {}},
// C1b: ab reported this one with NO className at all.
{"hwnd": 9004, "title": "bracket.SLDPRT", "pid": sw_pid, "rect": {}},
// M2(iii): a dialog ab reports with NO pid — unattributable. It stays in
// the census (a bare #32770 is a candidate) but must never be closed.
{"hwnd": 9005, "title": "Export", "className": "#32770", "rect": {}}
]));
// C1: the dialog that asks about the user's unsaved work. Closing it answers
// it — and by this bridge's own live evidence can take SOLIDWORKS down with
// every unsaved document in it.
let r = b.call("swx_close_window", json!({"hwnd": 9001, "reason": "tidying", "confirm": true}));
check!(
b,
"swx_close_window refuses a Save-changes dialog outright",
r["errorCode"] == "dialog_belongs_to_the_user",
s(&r)
);
let r = b.call("swx_close_window", json!({"hwnd": 9002, "reason": "tidying", "confirm": true}));
check!(b, "...and Document Recovery", r["errorCode"] == "dialog_belongs_to_the_user", s(&r));
// Even a dialog the bridge itself raised needs the opt-in, exactly as the
// sibling verb requires.
let r = b.call("swx_close_window", json!({"hwnd": 9003, "reason": "my own export sheet", "confirm": true}));
check!(
b,
"swx_close_window needs the process-level opt-in even for the bridge's own dialog",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
// C1b: an unknown window class must count as a FRAME, not as a dialog. The
// old `!class.is_empty() &&` made an unreadable class mean "closable".
let r = b.call("swx_close_window", json!({"hwnd": 9004, "reason": "tidying", "confirm": true}));
check!(
b,
"a window with NO className is treated as a frame, not as a dialog",
r["errorCode"] == "window_is_application_frame",
s(&r)
);
// ...and swx_window_info must not advertise any of them as closable.
let wi = b.call("swx_window_info", json!({}));
let closable: Vec<i64> = wi["windows"]
.as_array()
.unwrap()
.iter()
.filter(|w| w["closable"] == true)
.map(|w| w["hwnd"].as_i64().unwrap_or(0))
.collect();
check!(
b,
"swx_window_info marks the user's dialogs and the no-class window NOT closable",
!closable.contains(&9001) && !closable.contains(&9002) && !closable.contains(&9004),
format!("closable: {closable:?}")
);
// M2(ii): SOLIDWORKS' main window, reported with the DIALOG class. Only the
// main-hwnd shortcut in `is_frame_window` catches this one — the class test
// would wave it through as a dialog.
ab.set_windows(json!([
{"hwnd": main, "title": "SOLIDWORKS", "className": "#32770", "pid": sw_pid, "rect": {}}
]));
let wi = b.call("swx_window_info", json!({}));
check!(
b,
"the main window is a FRAME even when ab reports it with the dialog class",
wi["windows"][0]["isFrame"] == true && wi["windows"][0]["closable"] == false,
s(&wi)
);
check!(
b,
"NOTHING was dispatched to ab for any of it",
ab.destructive_calls().is_empty(),
format!("{:?}", ab.destructive_calls())
);
// M2(iii): under the OPT-IN, an UNATTRIBUTABLE dialog must still be refused.
// Every stub window previously carried the SOLIDWORKS pid, so this branch
// was unreachable and a stranger's dialog closing would have gone unnoticed.
let ab2 = common::AbStub::start(json!([]));
let on = Bridge::start_with(&[("ADOM_DIRECT_API_URL", &ab2.url), ("SWX_ALLOW_USER_WRITES", "1")]);
on.call("swx_start", json!({}));
let st2 = on.call("swx_get_app_state", json!({}));
ab2.set_windows(json!([
{"hwnd": st2["mainHwnd"].as_i64().unwrap(), "title": "SOLIDWORKS", "className": "Afx:0001",
"pid": st2["processId"].as_i64().unwrap(), "rect": {}},
// A #32770 with SOMEBODY ELSE'S pid — Word's Save dialog, say.
{"hwnd": 7777, "title": "Export", "className": "#32770", "pid": 999_999, "rect": {}},
// ...and one ab reports with NO pid at all: a candidate, never a fact.
// This is the row that reaches the `ownerConfirmed` guard, which every
// previous stub made unreachable by giving every window the SW pid.
{"hwnd": 7778, "title": "Export", "className": "#32770", "rect": {}}
]));
let r = on.call("swx_dismiss_blocking_dialogs", json!({
"close": true, "confirm": true, "allowUserWrites": true, "reason": "tidying"
}));
check!(
on,
"even WITH the opt-in, a dialog owned by another process is not closed",
r["closed"].as_array().map(|c| c.is_empty()).unwrap_or(false),
s(&r)
);
check!(
on,
"...and an UNATTRIBUTABLE dialog is refused on that basis, not silently dropped",
r["refused"].as_array().unwrap().iter().any(|x| x["hwnd"] == 7778
&& x["why"].as_str().unwrap_or("").contains("cannot prove")),
s(&r)
);
check!(
on,
"...and nothing was dispatched for it",
ab2.destructive_calls().is_empty(),
format!("{:?}", ab2.destructive_calls())
);
}
#[test]
fn a_write_lock_on_the_users_file_is_a_write() {
// H1: round 7 changed swx_open's DEFAULT to read-only and left the harmful
// mode caller-settable, on the com host's safe list where authorize_command
// never runs. readOnly:false takes a WRITE LOCK on the user's file and
// converts-and-dirties an older-version document in memory, so SOLIDWORKS
// offers to save it on close.
let b = Bridge::start();
b.call("swx_start", json!({}));
// M2(iv): the DEFAULT itself had zero tests.
let r = b.call("swx_open", json!({"path": "C:\\users\\gearbox.sldasm"}));
check!(b, "swx_open succeeds on the user's document", r["success"] == true, s(&r));
check!(b, "...and is READ-ONLY by default", r["readOnly"] == true, s(&r));
let r = b.call("swx_open", json!({"path": "C:\\users\\other.sldprt", "readOnly": false}));
check!(
b,
"swx_open {readOnly:false} on the user's document is refused",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
// The bridge's OWN document opens for writing without ceremony.
let tmp = TempDir::new("openrw");
let mine = tmp.join("mine.sldprt");
b.call("swx_new_document", json!({"type": "part"}));
let saved = b.call("swx_save_as", json!({"outputPath": mine.to_string_lossy()}));
check!(b, "the bridge saved its own document", saved["success"] == true, s(&saved));
let r = b.call("swx_open", json!({"path": mine.to_string_lossy(), "readOnly": false}));
check!(b, "...and opens it for writing freely while it is ours", r["success"] == true, s(&r));
check!(b, "...reporting readOnly:false", r["readOnly"] == false, s(&r));
// Once CLOSED it is no longer tracked, by either veto, and reopening it for
// writing needs the opt-in again. Ownership means "a document this session
// created and still holds", not "a file this bridge once wrote" — the
// narrower reading is the one that cannot drift into someone else's file.
b.call("swx_close_doc", json!({"path": mine.to_string_lossy()}));
let r = b.call("swx_open", json!({"path": mine.to_string_lossy(), "readOnly": false}));
check!(
b,
"a CLOSED document is no longer ours to reopen for writing",
r["errorCode"] == "user_writes_disabled",
s(&r)
);
let r = b.call("swx_open", json!({"path": mine.to_string_lossy()}));
check!(b, "...though read-only always works", r["success"] == true, s(&r));
}
#[test]
fn the_screenshot_verb_captures_solidworks_and_nothing_else() {
// M1: `titleContains` was forwarded straight to ab, so a verb catalogued as
// "screenshot the SOLIDWORKS window" would capture any window on the
// desktop that matched. Not a write — but it is the user's data, it was
// ungated, and it had no test.
let ab = common::AbStub::start(json!([]));
let b = Bridge::start_with(&[("ADOM_DIRECT_API_URL", &ab.url)]);
b.call("swx_start", json!({}));
let state = b.call("swx_get_app_state", json!({}));
let sw_pid = state["processId"].as_i64().unwrap();
let main = state["mainHwnd"].as_i64().unwrap();
ab.set_windows(json!([
{"hwnd": main, "title": "SOLIDWORKS", "className": "Afx:0001", "pid": sw_pid, "rect": {}},
{"hwnd": 3131, "title": "1Password", "className": "Chrome_WidgetWin_1", "pid": 999_999, "rect": {}}
]));
let r = b.call("swx_screenshot", json!({"titleContains": "1Password"}));
check!(b, "swx_screenshot ignores a free-text title", r["hwnd"] == main, s(&r));
let r = b.call("swx_screenshot", json!({"hwnd": 3131}));
check!(
b,
"swx_screenshot refuses an hwnd it cannot attribute to SOLIDWORKS",
r["errorCode"] == "window_not_attributed",
s(&r)
);
let shots = ab.calls_to("desktop_screenshot_window");
check!(
b,
"every capture ab was asked for was BY HANDLE, and only SOLIDWORKS' own",
shots.iter().all(|c| c["args"]["hwnd"] == main && c["args"].get("titleContains").is_none()),
format!("{shots:?}")
);
}
#[test]
fn a_document_that_has_never_been_saved_is_addressable_everywhere() {
// THE LIVE BUG. `swx_new_document` produced a `Part2` that `swx_docs`
// listed and every other verb reported as "document not open" — including
// with no target at all, because the server always sends the resolved
// identity now. The whole create → model → export flow was dead for new
// documents, and 89 mock tests passed because the mock resolved titles from
// its own map instead of the way SOLIDWORKS does.
//
// Root cause: a pathless document's `path` is EMPTY, and
// `GetOpenDocumentByName` — which wants a full path — cannot find one by
// title. Round 4 made path the identity; that was right, and it
// over-corrected. For a document with no path the TITLE is the identity,
// which is exactly what my own round-3 rebuttal argued.
let b = Bridge::start();
b.call("swx_start", json!({}));
// A document the USER has open, to make sure the fix does not resolve by
// title so loosely that it starts matching theirs.
b.call("swx_open", json!({"path": "C:\\users\\gearbox.sldasm"}));
// `keepUnsaved:true`, because 0.6.0 gives every new document a file of its
// own at birth — precisely so this situation stops arising. The PATHLESS case
// is still reachable (and this is how), and everything it proves still has to
// hold for it: a document whose only name is its title must be addressable by
// that title, everywhere.
let made = b.call("swx_new_document", json!({"type": "part", "keepUnsaved": true}));
let title = made["created"]["title"].as_str().unwrap_or("").to_string();
check!(b, "swx_new_document created a part", made["success"] == true, s(&made));
check!(b, "...with NO path, which is the whole difficulty", made["created"]["path"] == "", s(&made));
// swx_docs lists it — the verb that always worked.
let docs = b.call("swx_docs", json!({}));
check!(
b,
"swx_docs lists the unsaved document",
docs["docs"].as_array().unwrap().iter().any(|d| d["title"] == json!(title.clone())),
s(&docs)
);
// ...and now so does everything else. These are the four calls that failed
// live, in the order they failed.
let r = b.call("swx_design_info", json!({"targetDocument": title.clone()}));
check!(b, "swx_design_info resolves it BY TITLE", r["success"] == true, s(&r));
let r = b.call("swx_design_info", json!({}));
check!(b, "swx_design_info resolves it as the ACTIVE document", r["success"] == true, s(&r));
check!(b, "...and it really is the one we made", r["doc"]["title"] == json!(title.clone()), s(&r));
// The create → model → export flow that never got to run live.
let r = b.call("swx_run_script", json!({"script": [
{"op": "sketch_rect", "plane": "Front Plane", "widthMm": 50, "heightMm": 30},
{"op": "extrude", "depthMm": 20}
], "targetDocument": title.clone()}));
check!(b, "swx_run_script builds into a pathless document", r["success"] == true, s(&r));
let r = b.call("swx_inspect_bodies", json!({"targetDocument": title.clone()}));
check!(b, "swx_inspect_bodies reads it back", r["bodyCount"] == 1, s(&r));
let tmp = TempDir::new("pathless");
let out = tmp.join("from-scratch.step");
let r = b.call("swx_export_step", json!({"outputPath": out.to_string_lossy(), "targetDocument": title.clone()}));
check!(b, "swx_export_step exports it", r["success"] == true && out.is_file(), s(&r));
// reset_workspace swept it, and close_doc can close it by name.
let r = b.call("swx_reset_workspace", json!({}));
check!(
b,
"swx_reset_workspace closes the pathless document it created",
r["closed"].as_array().unwrap().contains(&json!(title.clone())),
s(&r)
);
check!(
b,
"...and still skips the user's document",
r["skipped"].as_array().unwrap().iter().any(|x| x["title"] == "gearbox.sldasm"),
s(&r)
);
// close_doc by name, on a second one.
let made = b.call("swx_new_document", json!({"type": "part"}));
let t2 = made["created"]["title"].as_str().unwrap_or("").to_string();
let r = b.call("swx_close_doc", json!({"name": t2.clone(), "save": false}));
check!(b, "swx_close_doc closes a pathless document by name", r["success"] == true, s(&r));
let docs = b.call("swx_docs", json!({}));
check!(
b,
"...and it is gone",
!docs["docs"].as_array().unwrap().iter().any(|d| d["title"] == json!(t2)),
s(&docs)
);
// An EMPTY path must mean "no path", not "match any document that has no
// path" — with two unsaved documents open, the latter silently picks one.
let a = b.call("swx_new_document", json!({"type": "part"}));
let ta = a["created"]["title"].as_str().unwrap_or("").to_string();
let c = b.call("swx_new_document", json!({"type": "part"}));
let tc = c["created"]["title"].as_str().unwrap_or("").to_string();
check!(b, "two unsaved documents are open", ta != tc && !ta.is_empty(), format!("{ta} {tc}"));
let r = b.call("swx_design_info", json!({"targetDocument": tc.clone(), "path": ""}));
check!(
b,
"an empty path is ignored and the TITLE decides which document",
r["doc"]["title"] == json!(tc.clone()),
s(&r)
);
b.call("swx_close_doc", json!({"name": ta, "save": false}));
b.call("swx_close_doc", json!({"name": tc, "save": false}));
// A name nothing answers to must still be a BRANCHABLE refusal, not prose.
let r = b.call("swx_design_info", json!({"targetDocument": "NoSuchDocument"}));
check!(b, "a missing document is refused with a stable code", r["errorCode"] == "document_not_open", s(&r));
}
#[test]
fn the_bridge_can_clean_up_a_scratch_document_it_has_saved() {
// THE ACCEPTANCE TEST for live run 2's headline bug. The suite created a
// part, modelled it, saved it — and the bridge then refused to close it:
// "would close 'smoke.SLDPRT', which this bridge did NOT create". It had
// created it. The moment save_as gave the document a path, the ownership
// record (holding the path we ASKED for, `smoke.sldprt`) stopped matching
// the path SOLIDWORKS REPORTS (`smoke.SLDPRT`), and the bridge disowned its
// own scratch document — so it could neither touch nor clean it up. That
// cascaded into four of the nine failures and left an orphan on the machine.
//
// Ownership follows the DOCUMENT, not the string that used to name it.
let b = Bridge::start();
b.call("swx_start", json!({}));
let tmp = TempDir::new("acceptance");
let out = tmp.join("smoke.sldprt"); // lowercase, as a caller writes it
let made = b.call("swx_new_document", json!({"type": "part"}));
let title = made["created"]["title"].as_str().unwrap_or("").to_string();
b.call("swx_run_script", json!({"script": [
{"op": "sketch_rect", "plane": "Front Plane", "widthMm": 50, "heightMm": 30},
{"op": "extrude", "depthMm": 20}
], "targetDocument": title.clone()}));
let saved = b.call("swx_save_as", json!({"outputPath": out.to_string_lossy(), "targetDocument": title.clone()}));
check!(b, "the bridge saved its own scratch document", saved["success"] == true, s(&saved));
let reported = saved["doc"]["path"].as_str().unwrap_or("").to_string();
check!(
b,
"SOLIDWORKS reports the path with its own casing, not ours",
!reported.is_empty() && reported != out.to_string_lossy(),
format!("asked {} got {reported}", out.to_string_lossy())
);
// STILL OURS — by the reported path, by the requested path, and by title.
let now = saved["doc"]["title"].as_str().unwrap_or("").to_string();
let sweep = b.call("swx_reset_workspace", json!({"titles": ["nothing"]}));
check!(
b,
"our claim followed the document through the save",
sweep["trackedScratchDocuments"].as_array().unwrap().iter()
.any(|t| t.as_str().unwrap_or("").eq_ignore_ascii_case(&now)),
s(&sweep)
);
// Every route the live run tried, now working.
let r = b.call("swx_set_preference", json!({"name": "lengthUnitRaw", "value": 1, "path": reported.clone()}));
check!(b, "swx_set_preference works on the saved scratch document", r["success"] == true, s(&r));
let r = b.call("swx_run_script", json!({"script": [{"op": "rebuild"}], "targetDocument": now.clone()}));
check!(b, "swx_run_script works on it", r["success"] == true, s(&r));
// THE acceptance: close it by the path the CALLER wrote, casing and all.
let r = b.call("swx_close_doc", json!({"path": out.to_string_lossy(), "save": false}));
check!(
b,
"swx_close_doc closes the bridge's own saved scratch document",
r["success"] == true,
s(&r)
);
let docs = b.call("swx_docs", json!({}));
check!(b, "...and nothing is left open", docs["docs"].as_array().unwrap().is_empty(), s(&docs));
}
#[test]
fn closing_by_path_is_unambiguous_even_when_titles_collide() {
// Live run 2's second bug: `swx_import` of the file the suite had just saved
// produced a SECOND document with the same title, so close-by-title
// correctly refused — but the bridge had manufactured the ambiguity itself.
// A PATH identifies exactly one document, so the path branch must resolve
// before the title-ambiguity check can fire.
let b = Bridge::start();
b.call("swx_start", json!({}));
let tmp = TempDir::new("collide");
let out = tmp.join("smoke.sldprt");
let made = b.call("swx_new_document", json!({"type": "part"}));
let title = made["created"]["title"].as_str().unwrap_or("").to_string();
let saved = b.call("swx_save_as", json!({"outputPath": out.to_string_lossy(), "targetDocument": title}));
let saved_title = saved["doc"]["title"].as_str().unwrap_or("").to_string();
let saved_path = saved["doc"]["path"].as_str().unwrap_or("").to_string();
// Import the very file we just saved: a second document, same title.
let imported = b.call("swx_import", json!({"filePath": out.to_string_lossy()}));
check!(b, "the import opened a document", imported["success"] == true, s(&imported));
let docs = b.call("swx_docs", json!({}));
let same_title = docs["docs"]
.as_array()
.unwrap()
.iter()
.filter(|d| d["title"].as_str().unwrap_or("").eq_ignore_ascii_case(&saved_title))
.count();
check!(b, "two open documents now share a title", same_title == 2, s(&docs));
// By TITLE: correctly refused.
let r = b.call("swx_close_doc", json!({"name": saved_title.clone(), "save": false}));
check!(b, "closing by the shared title is refused", r["errorCode"] == "ambiguous_target", s(&r));
// By PATH: unambiguous, and it closes the RIGHT one — the saved document,
// not the imported copy that has no path.
let r = b.call("swx_close_doc", json!({"path": saved_path.clone(), "save": false}));
check!(b, "closing by path succeeds despite the collision", r["success"] == true, s(&r));
let docs = b.call("swx_docs", json!({}));
check!(
b,
"...and the document that went is the SAVED one",
!docs["docs"].as_array().unwrap().iter().any(|d| d["path"] == json!(saved_path)),
s(&docs)
);
}
#[test]
fn what_the_human_authorizes_in_full_the_bridge_actually_does() {
// THE ACCEPTANCE TEST for live run 3's aftermath. Drew authorized closing
// ALL the leftover documents from three live runs, including his own blank
// Part1. The bridge was restarted with SWX_ALLOW_USER_WRITES=1 and every
// call carried allowUserWrites + confirm + reason. It closed 3 of 5.
//
// swx_close_doc -> "the com host refuses to close_doc: this
// document was not created by this bridge"
// swx_close_all_documents -> closed the clean ones, skipped the dirty
// foreign ones unconditionally
//
// The server AUTHORIZED the close and then told the host nothing: only the
// `save:true` branch built a Grant, so `close_doc {save:false}` went to the
// host with no evidence of the permission, and the host — holding its own
// independent veto, correctly — refused. Two vetoes are a safety property
// only while BOTH can be opened by the person who owns the machine.
let on = Bridge::start_with(&[("SWX_ALLOW_USER_WRITES", "1")]);
on.call("swx_start", json!({}));
let tmp = TempDir::new("theirs");
// Two documents that are emphatically NOT ours: files on disk that existed
// before the bridge did.
let a = tmp.join("their-part.sldprt");
let bfile = tmp.join("their-other.sldprt");
std::fs::write(&a, b"theirs").unwrap();
std::fs::write(&bfile, b"theirs too").unwrap();
on.call("swx_open", json!({"path": a.to_string_lossy()}));
on.call("swx_open", json!({"path": bfile.to_string_lossy()}));
// DEFAULT posture first, on a second bridge: the refusal must still be the
// refusal. A fix that opens the hatch must not leave it open.
{
let off = Bridge::start_with(&[("SWX_TEST_NO_LAUNCH", "0")]);
off.call("swx_start", json!({}));
let theirs = tmp.join("untouchable.sldprt");
std::fs::write(&theirs, b"theirs").unwrap();
off.call("swx_open", json!({"path": theirs.to_string_lossy()}));
let r = off.call("swx_close_doc", json!({
"name": "untouchable.sldprt", "save": false,
"confirm": true, "allowUserWrites": true, "reason": "trying it on"
}));
check!(
off,
"without the PROCESS-level opt-in the close is still refused",
r["success"] == false && r["errorCode"] == "user_writes_disabled",
s(&r)
);
let docs = off.call("swx_docs", json!({}));
check!(
off,
"...and the document is still open",
docs["docs"].as_array().unwrap().len() == 1,
s(&docs)
);
}
// Opted in, in full: it closes. This is the exact call that failed live.
let r = on.call("swx_close_doc", json!({
"name": "their-part.sldprt", "save": false,
"confirm": true, "allowUserWrites": true,
"reason": "Drew asked me to close the leftovers from the live runs"
}));
check!(
on,
"the full opt-in closes a document the bridge did not create",
r["success"] == true,
s(&r)
);
// And the grant REACHED the host: the host's veto is the thing that fired
// live, so a pass here that came from the server alone would prove nothing.
check!(
on,
"...and the host was told, so its own veto did not fire",
r["error"].as_str().map(|e| !e.contains("com host refuses")).unwrap_or(true),
s(&r)
);
// The ledger records the exception for the human, with their reason.
let led = on.call("swx_action_ledger", json!({}));
check!(
on,
"the granted exception is in the human's ledger with the reason given",
s(&led).contains("Drew asked me to close the leftovers"),
s(&led)
);
// Now the bulk verb, on a dirty foreign document — the other live failure.
let dirtied = on.call("swx_props_set", json!({
"path": bfile.to_string_lossy(), "properties": {"Revision": "B"},
"confirm": true, "allowUserWrites": true, "reason": "make it dirty for the test"
}));
check!(on, "the foreign document was modified in memory", dirtied["success"] == true, s(&dirtied));
let docs = on.call("swx_docs", json!({}));
check!(
on,
"the foreign document is dirty and still open",
docs["docs"][0]["dirty"] == true,
s(&docs)
);
let r = on.call("swx_close_all_documents", json!({
"confirm": true, "closeUnsaved": true,
"allowUserWrites": true,
"reason": "Drew authorized closing every leftover, unsaved ones included"
}));
check!(
on,
"closeUnsaved with the full opt-in closes the user's dirty document too",
r["success"] == true && r["discarded"].as_array().unwrap().len() == 1,
s(&r)
);
let docs = on.call("swx_docs", json!({}));
check!(
on,
"SOLIDWORKS is left with nothing open — 5 of 5, not 3 of 5",
docs["docs"].as_array().unwrap().is_empty(),
s(&docs)
);
check!(
on,
"discarding the user's unsaved work is recorded for them",
s(&on.call("swx_action_ledger", json!({}))).contains("user_document_changes_discarded"),
s(&r)
);
}
#[test]
fn close_all_documents_accounts_for_every_document_it_leaves_open() {
// Live run 3's follow-up: `closeUnsaved:true` left two documents behind and
// the response explained neither. `skipped` was empty (the foreign dirty
// pair never entered the loop, because a document whose dirtiness
// SOLIDWORKS would not report was read as CLEAN via `unwrap_or(false)`),
// and the host's `remaining` came back as a bare list with no reasons. A
// caller saw success, saw documents still open, and had nothing to act on.
// `swx_close_all_documents` ends the user's session, so it needs the
// process-level opt-in to run at all — the accounting question is what it
// says about documents it leaves behind once it IS allowed to run.
let b = Bridge::start_with(&[("SWX_ALLOW_USER_WRITES", "1")]);
b.call("swx_start", json!({}));
let tmp = TempDir::new("accounting");
let theirs = tmp.join("still-theirs.sldprt");
std::fs::write(&theirs, b"theirs").unwrap();
b.call("swx_open", json!({"path": theirs.to_string_lossy()}));
// Dirty, so SOLIDWORKS will decline to close it without being told to
// discard the changes.
b.call("swx_props_set", json!({
"path": theirs.to_string_lossy(), "properties": {"Revision": "C"},
"confirm": true, "allowUserWrites": true, "reason": "make it dirty for the test"
}));
let mine = b.call("swx_new_document", json!({"type": "part"}));
let mine = mine["created"]["title"].as_str().unwrap_or("").to_string();
// closeUnsaved NOT set: the user's dirty document must survive, WITH an
// explanation naming it.
let r = b.call("swx_close_all_documents", json!({
"confirm": true, "allowUserWrites": true,
"reason": "clearing the bridge's own scratch"
}));
check!(b, "the bulk close succeeds", r["success"] == true, s(&r));
check!(
b,
"the bridge's own clean document was closed",
!r["closed"].as_array().map(|a| a.is_empty()).unwrap_or(true)
&& !s(&b.call("swx_docs", json!({}))).contains(&mine),
s(&r)
);
let docs = b.call("swx_docs", json!({}));
let left: Vec<String> = docs["docs"]
.as_array()
.unwrap()
.iter()
.map(|d| d["title"].as_str().unwrap_or("").to_string())
.collect();
let skipped = r["skipped"].as_array().cloned().unwrap_or_default();
check!(
b,
"the user's document was NOT closed",
left.iter().any(|t| t == "still-theirs.sldprt"),
s(&docs)
);
// THE INVARIANT: nothing is left open without an entry naming it.
for title in &left {
check!(
b,
&format!("'{title}' is left open AND accounted for in `skipped`"),
skipped.iter().any(|e| e["title"].as_str() == Some(title.as_str())),
format!("left={left:?} skipped={}", serde_json::to_string(&skipped).unwrap())
);
}
check!(
b,
"...and each entry says why, and how the user could close it",
skipped.iter().all(|e| e["why"].as_str().map(|w| !w.is_empty()).unwrap_or(false)),
serde_json::to_string(&skipped).unwrap()
);
}
#[test]
fn asking_solidworks_to_quit_is_not_the_same_as_it_quitting() {
// Live: `swx_stop {force:true, confirm, allowUserWrites, reason}` answered
// `success:true, stopped:false` — declined, silently, with no errorCode and
// no reason. A caller (and an AI planning its next step) reads that as
// done. The host had done its job: it re-attaches after ExitApp and reports
// honestly that SOLIDWORKS is still there. The SERVER then dressed that up
// as a success.
let machine = quittable_machine("stop-honesty", r#"{"a":"exited"}"#);
let b = Bridge::start_with(&[
("SWX_ALLOW_USER_WRITES", "1"),
("SWX_LAUNCH_FAKE_DIR", &machine.path()),
("SWX_QUIT_SETTLE_MS", "80"),
]);
// Not started: the host's own "nothing to stop" answer is the same shape.
let r = b.call("swx_stop", json!({
"force": true, "confirm": true, "allowUserWrites": true, "reason": "tidy up"
}));
check!(
b,
"a stop that did not stop anything is reported as a FAILURE",
r["success"] == false,
s(&r)
);
check!(
b,
"...with a stable errorCode",
r["errorCode"].is_string() && r["errorCode"] != "",
s(&r)
);
check!(
b,
"...and the host's own reason is preserved rather than replaced",
r["stopped"] == false && r["reason"] == "not running",
s(&r)
);
check!(
b,
"...and the hint does not send the caller to swx_kill",
!s(&r).contains("use swx_kill"),
s(&r)
);
// The success path still reads as a success.
b.call("swx_start", json!({}));
let r = b.call("swx_stop", json!({
"confirm": true, "allowUserWrites": true, "reason": "done with SOLIDWORKS"
}));
check!(b, "a stop that DID stop it succeeds", r["success"] == true && r["stopped"] == true, s(&r));
}
#[test]
fn solidworks_connected_can_be_started_through_the_platform_shortcut() {
// THE LAUNCH ANSWER (2026-09-03). This bridge spent three live rounds
// reporting `host_app_launch_blocked` on the grounds that SOLIDWORKS
// Connected "cannot be launched programmatically". That conclusion came
// from the wrong experiment: we called CoCreateInstance on
// SldWorks.Application, which starts sldworks.exe DIRECTLY, and the
// application refused because it had been given no tenant and no registry.
// Reading the Start-Menu shortcut shows the sanctioned chain —
//
// CATSTART.exe -run "SWXDesktopLauncher.exe"
// -object "--AppName=\"SWXCSWK_AP\" -tenant=<TENANT> -monoapp
// -3DRegistryURL=https://<region>-makers-registry..."
// -nowindow
//
// — so the refusal was OURS, for bypassing the launcher. Invoking the user's
// own shortcut runs exactly what Dassault intends, with their tenant, and
// nothing is reverse-engineered.
let tmp = TempDir::new("shortcut");
let lnk = tmp.join("SOLIDWORKS Design.lnk");
std::fs::write(&lnk, b"shortcut").unwrap();
let lnk = lnk.to_string_lossy().to_string();
// ── the machine has a shortcut, and SOLIDWORKS comes up ─────────────────
{
let b = Bridge::start_with(&[
("SWX_SOLIDWORKS_SHORTCUT", &lnk),
("SWX_MOCK_SHORTCUT_ATTACHES", "1"),
]);
let st = b.call("swx_status", json!({}));
check!(
b,
"swx_status reports the shortcut it found and the route it would take",
st["solidworks"]["shortcut"] == lnk.as_str()
&& st["solidworks"]["launchRoute"] == "platformShortcut",
s(&st)
);
// GATED AS A SESSION-STARTING ACTION: a reason, and nothing more. It
// runs a program on somebody's desktop and signs in to their tenant, so
// it is recorded for them — but it creates nothing and destroys nothing,
// so it does not go near the write choke point.
let r = b.call("swx_start", json!({}));
check!(
b,
"launching through the shortcut needs a reason written for the human",
r["success"] == false && r["errorCode"] == "reason_required",
s(&r)
);
check!(
b,
"...and it does NOT demand the write opt-in, which starting an app should not need",
!s(&r).contains("allowUserWrites"),
s(&r)
);
let r = b.call("swx_start", json!({"reason": "Drew asked me to model a bracket"}));
check!(
b,
"with a reason, SOLIDWORKS Connected STARTS — the launch answer",
r["success"] == true && r["started"] == true && r["attached"] == true,
s(&r)
);
check!(
b,
"...by the sanctioned route, named in the response",
r["launchRoute"] == "platformShortcut" && r["shortcut"] == lnk.as_str(),
s(&r)
);
check!(
b,
"...and it is genuinely usable, not just 'launched'",
b.call("swx_new_document", json!({"type": "part"}))["success"] == true,
s(&r)
);
check!(
b,
"the launch is recorded for the human with the route and the reason",
s(&b.call("swx_action_ledger", json!({}))).contains("platformShortcut"),
s(&r)
);
}
// ── the launcher was invoked but is still coming up ─────────────────────
// A Connected cold start runs one to three minutes and may stop at a
// sign-in nobody has answered. The verb must NOT hold the com host for
// that: it is the server's only channel, so blocking it would freeze
// swx_progress and every other verb. And it must not report a FAILURE —
// that invites a second launch, which means two sign-in windows.
{
let b = Bridge::start_with(&[("SWX_SOLIDWORKS_SHORTCUT", &lnk)]);
// waitSeconds:0 — do not block at all, so this is the answer a caller
// gets the instant the launcher has been fired.
let r = b.call("swx_start", json!({
"reason": "starting SOLIDWORKS for Drew", "waitSeconds": 0
}));
check!(
b,
"a launch still in flight is not a failure",
r["success"] == true && r["started"] == true,
s(&r)
);
check!(
b,
"...it says so plainly, so the caller polls instead of launching again",
r["stillLaunching"] == true && r["attached"] == false,
s(&r)
);
check!(
b,
"...and the hint warns against a second launch",
s(&r).contains("two sign-in windows"),
s(&r)
);
// The bridge is still ANSWERING — the whole point of not blocking.
check!(b, "the bridge is still responsive", b.call("swx_status", json!({}))["success"] == true, s(&r));
}
// ── the launcher was invoked and SOLIDWORKS arrives WHILE WE WAIT ───────
// The attach poll, which is the reason this verb is not a one-shot. The
// mock's launcher becomes attachable after a delay, exactly as a Connected
// cold start does, so the loop is genuinely exercised rather than asserted.
{
let b = Bridge::start_with(&[
("SWX_SOLIDWORKS_SHORTCUT", &lnk),
("SWX_MOCK_SHORTCUT_ATTACHES_AFTER_MS", "3000"),
]);
let r = b.call("swx_start", json!({
"reason": "starting SOLIDWORKS for Drew", "waitSeconds": 20
}));
check!(
b,
"the poll waits out a slow launcher and attaches when it arrives",
r["success"] == true && r["attached"] == true && r["stillLaunching"] == false,
s(&r)
);
check!(
b,
"...having actually waited for it, not guessed",
r["elapsedSeconds"].as_u64().unwrap_or(0) >= 3,
s(&r)
);
check!(
b,
"...and only ONE launch was made, however many times we probed",
b.call("swx_docs", json!({}))["success"] == true,
s(&r)
);
}
// ── no shortcut anywhere: blocked, and ONLY then ────────────────────────
{
let b = Bridge::start_with(&[("SWX_TEST_NO_LAUNCH", "1")]);
let r = b.call("swx_start", json!({"reason": "trying"}));
check!(
b,
"with no shortcut to invoke, the bridge asks the user — the one remaining block",
r["success"] == false && r["errorCode"] == "host_app_launch_blocked",
s(&r)
);
check!(
b,
"...and says the block is a MISSING SHORTCUT, not an impossible edition",
s(&r).contains("could not FIND that shortcut")
&& s(&r).contains("SWX_SOLIDWORKS_SHORTCUT"),
s(&r)
);
}
}
#[test]
fn a_refused_bulk_close_closes_nothing_at_all() {
// Found while proving the fix above. `swx_close_all_documents` ran its
// discard loop and only THEN asked whether it was allowed to end the
// session — so in the DEFAULT posture it closed the bridge's own dirty
// documents, discarding their contents, and returned a refusal. Side
// effects on the way to "no". A refusal has to mean nothing happened.
let b = Bridge::start();
b.call("swx_start", json!({}));
let made = b.call("swx_new_document", json!({"type": "part"}));
let title = made["created"]["title"].as_str().unwrap_or("").to_string();
b.call("swx_run_script", json!({"script": [
{"op": "sketch_rect", "plane": "Front Plane", "widthMm": 40, "heightMm": 40},
{"op": "extrude", "depthMm": 10}
], "targetDocument": title.clone()}));
let docs = b.call("swx_docs", json!({}));
check!(b, "the bridge has one dirty document of its own", docs["docs"][0]["dirty"] == true, s(&docs));
let r = b.call("swx_close_all_documents", json!({
"confirm": true, "closeUnsaved": true, "reason": "clearing up"
}));
check!(
b,
"the bulk close is refused without the process-level opt-in",
r["success"] == false && r["errorCode"] == "user_writes_disabled",
s(&r)
);
let docs = b.call("swx_docs", json!({}));
check!(
b,
"AND NOTHING WAS CLOSED — a refusal that already destroyed work is not a refusal",
docs["docs"].as_array().unwrap().len() == 1 && docs["docs"][0]["title"] == title.as_str(),
s(&docs)
);
check!(
b,
"...and the work is still there, not silently discarded",
docs["docs"][0]["dirty"] == true,
s(&docs)
);
}
#[test]
fn a_document_solidworks_will_not_answer_for_is_treated_as_unsaved() {
// SOLIDWORKS does not always report whether a document is dirty. Every
// other verb treats an unanswerable question as "dirty" — swx_close_doc
// says so in as many words — but `swx_close_all_documents` read the field
// with `unwrap_or(false)`, so a document whose state could not be
// established was classified CLEAN. It then never entered the discard loop,
// `CloseAllDocuments(false)` left it alone because it did in fact need
// saving, and it came back in the host's `remaining` — left open by a call
// that had been told to close everything, with nothing anywhere saying why.
// That is one of the two documents live run 3's cleanup could not shift.
let b = Bridge::start_with(&[
("SWX_ALLOW_USER_WRITES", "1"),
("SWX_MOCK_DIRTY_UNKNOWN", "1"),
]);
b.call("swx_start", json!({}));
let tmp = TempDir::new("unknown-dirty");
let theirs = tmp.join("unanswerable.sldprt");
std::fs::write(&theirs, b"theirs").unwrap();
b.call("swx_open", json!({"path": theirs.to_string_lossy()}));
b.call("swx_props_set", json!({
"path": theirs.to_string_lossy(), "properties": {"Revision": "D"},
"confirm": true, "allowUserWrites": true, "reason": "make it genuinely unsaved"
}));
let docs = b.call("swx_docs", json!({}));
check!(
b,
"SOLIDWORKS will not say whether this document is dirty",
docs["docs"][0]["dirty"].is_null(),
s(&docs)
);
let r = b.call("swx_close_all_documents", json!({
"confirm": true, "closeUnsaved": true, "allowUserWrites": true,
"reason": "Drew authorized clearing every leftover"
}));
check!(b, "the bulk close succeeds", r["success"] == true, s(&r));
let docs = b.call("swx_docs", json!({}));
check!(
b,
"a document whose state is UNKNOWN is closed as unsaved, not skipped as clean",
docs["docs"].as_array().unwrap().is_empty(),
format!("{} / {}", s(&r), s(&docs))
);
check!(
b,
"...and it is recorded as a discard, because that is what it might have been",
s(&b.call("swx_action_ledger", json!({}))).contains("user_document_changes_discarded"),
s(&r)
);
}
#[test]
fn running_a_macro_is_arbitrary_code_and_is_gated_as_such() {
// `swx_run_macro` wraps ISldWorks::RunMacro2. A macro is a VBA project with
// the whole SOLIDWORKS API in scope and the filesystem behind it: it can
// model, save over anything, batch-export, delete or quit, to documents this
// bridge has never seen. The two questions the write choke point asks of
// everything else — is the document ours, is the destination clear — cannot
// bound it. So there is NO safe case, and this verb is always gated.
let tmp = TempDir::new("macros");
let mac = tmp.join("adom-hello.swp");
std::fs::write(&mac, b"' a macro").unwrap();
let mac = mac.to_string_lossy().to_string();
// ── the default posture refuses, and keeps refusing ─────────────────────
{
let off = Bridge::start();
off.call("swx_start", json!({}));
// Even fully "confirmed" by the caller: an AI cannot grant itself the
// right to run arbitrary code on somebody's desktop.
let r = off.call("swx_run_macro", json!({
"path": mac.clone(), "confirm": true, "allowUserWrites": true,
"reason": "I would like to run this"
}));
// And the gate comes FIRST: an unauthorized caller does not even learn
// whether the path exists.
let probe = off.call("swx_run_macro", json!({
"path": tmp.join("does-not-exist.swp").to_string_lossy(),
"confirm": true, "allowUserWrites": true, "reason": "probing"
}));
check!(
off,
"the gate is checked BEFORE the filesystem, so a refusal leaks nothing about it",
probe["errorCode"] == "user_writes_disabled",
s(&probe)
);
check!(
off,
"the default posture refuses to run a macro, however it is asked",
r["success"] == false && r["errorCode"] == "user_writes_disabled",
s(&r)
);
check!(
off,
"...and says what it is refusing: arbitrary code, not a document write",
s(&r).contains("arbitrary code"),
s(&r)
);
check!(
off,
"...and names the process-level opt-in it would need",
r["requires"]["env"] == "SWX_ALLOW_USER_WRITES=1",
s(&r)
);
}
// ── opted in: it runs, and every partial chain still refuses ────────────
let on = Bridge::start_with(&[("SWX_ALLOW_USER_WRITES", "1")]);
on.call("swx_start", json!({}));
for (missing, args) in [
("nothing at all", json!({"path": mac.clone()})),
("confirm and reason", json!({"path": mac.clone(), "allowUserWrites": true})),
("a reason", json!({"path": mac.clone(), "allowUserWrites": true, "confirm": true})),
("allowUserWrites", json!({"path": mac.clone(), "confirm": true, "reason": "why"})),
] {
let r = on.call("swx_run_macro", args);
check!(
on,
&format!("a call missing {missing} does not run a macro"),
r["success"] == false,
s(&r)
);
}
// A path that does not exist is caught HERE, not turned into a bare
// RunMacro2 error code.
let r = on.call("swx_run_macro", json!({
"path": tmp.join("nope.swp").to_string_lossy(),
"allowUserWrites": true, "confirm": true, "reason": "Drew asked for it"
}));
check!(
on,
"a missing macro is a clear error, not a numeric run code",
r["success"] == false && r["errorCode"] == "macro_not_found",
s(&r)
);
// THE WHOLE CHAIN: it runs.
let r = on.call("swx_run_macro", json!({
"path": mac.clone(),
"allowUserWrites": true, "confirm": true,
"reason": "Drew asked me to run his hello macro"
}));
check!(
on,
"with the full opt-in the macro runs",
r["success"] == true && r["ran"] == true && r["runError"] == 0,
s(&r)
);
check!(
on,
"the macro is unloaded afterwards, so an edited macro does not run its old code",
r["unloadedAfterRun"] == true,
s(&r)
);
check!(
on,
"the response admits this verb has never run on a live machine",
r["provenLive"] == false && s(&r).contains("NOT YET PROVEN ON A LIVE MACHINE"),
s(&r)
);
check!(
on,
"the run is recorded for the human with the reason they gave",
s(&on.call("swx_action_ledger", json!({}))).contains("Drew asked me to run his hello macro"),
s(&r)
);
// A module/procedure that is not in the project: RunMacro2 returns FALSE
// with the reason ONLY in the out parameter, which is the detail most
// easily got wrong.
let r = on.call("swx_run_macro", json!({
"path": mac.clone(), "module": "main", "procedure": "noSuchProc",
"allowUserWrites": true, "confirm": true, "reason": "Drew asked for it"
}));
check!(
on,
"a missing procedure surfaces the swRunMacroError_e out parameter, not just 'false'",
r["ran"] == false && r["runError"] == 1 && r["runErrorText"].as_str().unwrap_or("").contains("could not be found"),
s(&r)
);
// Not a macro at all.
let notmac = tmp.join("thing.txt");
std::fs::write(¬mac, b"x").unwrap();
let r = on.call("swx_run_macro", json!({
"path": notmac.to_string_lossy(),
"allowUserWrites": true, "confirm": true, "reason": "Drew asked for it"
}));
check!(on, "a file that is not a macro is refused", r["success"] == false, s(&r));
}
// ════════════════════════════════════════════════════════════════════════════
// A GUARD THAT NOTHING CALLS IS A COMMENT WITH A TEST ATTACHED
// ════════════════════════════════════════════════════════════════════════════
/// Stage a launch record whose attribution is about a DIFFERENT SOLIDWORKS than
/// the one COM will answer for. This is the shape a stale or wrong claim has, and
/// the three verbs that can end or empty a session must all refuse it.
fn stage_a_claim_about_another_process(scratch: &TempDir) {
std::fs::write(
scratch.join("launch-state.json"),
json!({
"attempt": {
"id": 1,
"startedAt": 1_700_000_000u64,
"shortcut": "SOLIDWORKS Design.lnk",
"route": "shellExecute",
"invokedAt": 1_700_000_000u64,
"stage": "attached",
"finished": true,
"outcome": "attachedAfterSignIn",
"runBeginSeen": true,
// THE CLAIM: a process this bridge says it launched — which is
// NOT the one the mock will report as attached.
"attributedSolidworks": {"pid": 424242, "createdAt": 1_700_000_001u64},
},
"preferredRoute": "shellExecute",
"consecutiveFailures": 0,
})
.to_string(),
)
.unwrap();
}
/// WIRING, not the function. `identity_matches_attribution` shipped in 0.6.7
/// unit-tested and with ZERO production callers — the fifth guard in this project
/// to do that, and the third about attribution. These tests go through the VERBS,
/// so deleting the call site (not the function) fails them.
#[test]
fn swx_stop_refuses_when_the_launch_claim_names_another_session() {
let scratch = TempDir::new("claim-stop");
stage_a_claim_about_another_process(&scratch);
// A machine whose session this bridge CAN identify: without one the live
// identity is unknown, and an unknown refuses for that reason instead —
// which would leave the claim check unexercised.
let machine = quittable_machine("claim-stop-machine", r#"{"a":"exited"}"#);
let b = Bridge::start_in(
TempDir::borrowed(&scratch.0),
&[("SWX_ALLOW_USER_WRITES", "1"), ("SWX_LAUNCH_FAKE_DIR", &machine.path()),
("SWX_QUIT_SETTLE_MS", "80")],
);
b.call("swx_start", json!({}));
let r = b.call("swx_stop", json!({
"confirm": true, "allowUserWrites": true, "force": true,
"reason": "the caller asked to end the session",
}));
check!(
b,
"swx_stop refuses a session its own launch record does not cover",
r["errorCode"] == "identity_mismatch",
s(&r)
);
check!(
b,
"...and names both processes",
r["error"].as_str().unwrap_or("").contains("424242"),
s(&r)
);
}
#[test]
fn swx_close_all_documents_refuses_when_the_launch_claim_names_another_session() {
let scratch = TempDir::new("claim-closeall");
stage_a_claim_about_another_process(&scratch);
// A machine whose session this bridge CAN identify: without one the live
// identity is unknown, and an unknown refuses for that reason instead —
// which would leave the claim check unexercised.
let machine = quittable_machine("claim-closeall-machine", r#"{"a":"exited"}"#);
let b = Bridge::start_in(
TempDir::borrowed(&scratch.0),
&[("SWX_ALLOW_USER_WRITES", "1"), ("SWX_LAUNCH_FAKE_DIR", &machine.path()),
("SWX_QUIT_SETTLE_MS", "80")],
);
b.call("swx_start", json!({}));
let r = b.call("swx_close_all_documents", json!({
"confirm": true, "allowUserWrites": true, "closeUnsaved": true,
"reason": "the caller asked for a clean session",
}));
check!(
b,
"swx_close_all_documents refuses it too",
r["errorCode"] == "identity_mismatch",
s(&r)
);
}
#[test]
fn swx_reset_workspace_refuses_when_the_launch_claim_names_another_session() {
let scratch = TempDir::new("claim-reset");
stage_a_claim_about_another_process(&scratch);
// A machine whose session this bridge CAN identify: without one the live
// identity is unknown, and an unknown refuses for that reason instead —
// which would leave the claim check unexercised.
let machine = quittable_machine("claim-reset-machine", r#"{"a":"exited"}"#);
let b = Bridge::start_in(
TempDir::borrowed(&scratch.0),
&[("SWX_LAUNCH_FAKE_DIR", &machine.path())],
);
b.call("swx_start", json!({}));
let r = b.call("swx_reset_workspace", json!({"reason": "clearing our scratch documents"}));
check!(
b,
"swx_reset_workspace refuses it too — it closes documents by name in that session",
r["errorCode"] == "identity_mismatch",
s(&r)
);
}
/// ...and with NO claim recorded, nothing changes: the ordinary rules decide.
#[test]
fn with_no_launch_claim_the_ordinary_rules_still_decide() {
let b = Bridge::start();
b.call("swx_start", json!({}));
let r = b.call("swx_reset_workspace", json!({"reason": "clearing"}));
check!(
b,
"a bridge that never launched anything is not blocked by the claim check",
r["errorCode"] != "identity_mismatch",
s(&r)
);
}