main
Drew Owens 0.9.0: bridge data moved out of the install cache (recovery survives updates), closed-in-place records, gates route thr… 5d02ca9 20d ago
//! **3D INTERCONNECT DECIDES HOW A NEUTRAL FILE COMES IN, AND IT IS THE USER'S
//! SETTING.**
//!
//! Measured live 2026-09-17, reading `GetUserPreferenceToggle` off the running
//! application with ids taken from the installed
//! `SolidWorks.Interop.swconst.dll` at
//! `C:\Program Files\Dassault Systemes\SOLIDWORKS 3DEXPERIENCE R2026x\SOLIDWORKS\`:
//!
//! ```text
//! 691  swMultiCAD_Enable3DInterconnect        TRUE
//! 291  swImportAutoRunImportDiagnostics       TRUE
//! 690  swImportNeutralRunDiagnostics          TRUE
//! 639  swForceEnableImportDiagnosis           FALSE
//! ```
//!
//! With 691 ON — the default — SOLIDWORKS does not translate a STEP through
//! `LoadFile4`/`LoadFile2` at all. It OPENS it as a part holding a LINKED 3D
//! Interconnect feature. That is why `LoadFile4` executed cleanly once 0.7.9
//! fixed the variant encoding and still returned no document with `errors = 1`
//! (`swGenericError`).
//!
//! The rule this file mostly exists to hold: **the bridge reads that preference
//! and never writes it.** Not set, and not flipped-and-restored — a crash between
//! the halves would leave Drew's SOLIDWORKS configured differently than he left
//! it.

mod common;

use common::{Bridge, TempDir};
use serde_json::{json, Value};

fn s(v: &Value) -> String {
    serde_json::to_string_pretty(v).unwrap_or_default()
}

fn a_step(scratch: &TempDir, name: &str) -> String {
    let p = scratch.join(name);
    std::fs::write(&p, "ISO-10303-21;\n").unwrap();
    p.to_string_lossy().to_string()
}

/// WITH 3D INTERCONNECT ON, NOTHING IMPORTS — AND THE BRIDGE SAYS SO BY NAME.
///
/// The matrix of 2026-09-17, on a real STEP with the toggle TRUE:
///
/// ```text
/// LoadFile4, Nothing                     -> no document, errors=1
/// LoadFile4, GetImportFileData object    -> no document, errors=1
/// LoadFile2                              -> false
/// OpenDoc6 docType 1, options 0/1/2/16   -> no document, errors=2097152 (requires repair)
/// OpenDoc6 docType 0, options 1          -> no document, errors=1024 (invalid file type)
/// ```
///
/// So there is no programmatic neutral-file import route on this edition while
/// that setting is on, and the imports that worked from 09-05 to 09-11 must have
/// run with it off. `auto` still makes ONE `LoadFile4` attempt — a future edition
/// that accepts it will simply work — and then reports what is ACTUALLY wrong.
/// Reporting a translator failure instead would send the reader hunting for a
/// missing translator that is not the problem.
#[test]
fn an_import_blocked_by_the_setting_says_which_setting() {
    let scratch = TempDir::new("interconnect-blocked");
    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({}));
    std::fs::write(b.scratch.join("mock-interconnect-blocks-import"), "").unwrap();
    let before = b.call("swx_docs", json!({}));
    let step = a_step(&scratch, "F-W10.step");

    let r = b.call("swx_import", json!({"path": step}));
    check!(b, "the import fails", r["success"] == false, s(&r));
    check!(
        b,
        "...with a code that names the cause, not a translator",
        r["errorCode"] == "interconnect_blocks_import",
        s(&r)
    );
    check!(
        b,
        "...telling the user exactly where the setting lives",
        r["detail"].as_str().unwrap_or("").contains("Enable 3D Interconnect"),
        s(&r)
    );
    check!(
        b,
        "...and that the bridge will not change it for them",
        r["detail"].as_str().unwrap_or("").contains("DOES NOT CHANGE USER SETTINGS"),
        s(&r)
    );
    // NOTHING WAS OPENED AND NOTHING WAS CHANGED.
    let after = b.call("swx_docs", json!({}));
    check!(b, "the session is untouched", after["count"] == before["count"], s(&after));
}

/// A DOCUMENT OPENED BY ANY ROUTE IS STILL OURS TO IDENTIFY AND DRAIN. The route
/// changed twice in two releases; ownership at birth did not, and the drain gate
/// depends on it.
#[test]
fn a_document_opened_by_any_import_route_is_still_ours() {
    let scratch = TempDir::new("interconnect-ownership");
    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({}));
    let step = a_step(&scratch, "F-W10.step");

    // The OpenDoc6 route, asked for by name — a measurement route now, but one
    // that still opens a document, and anything this bridge opens it must own.
    let r = b.call("swx_import", json!({"path": step, "route": "opendoc6"}));
    check!(b, "the named route ran", r["via"] == "OpenDoc6 (3D Interconnect)", s(&r));
    let identity = r["documentIdentity"].clone();
    check!(b, "the document is identified as it is born", identity["identity"] != "unknown", s(&r));

    let machine = TempDir::new("interconnect-ownership-machine");
    std::fs::write(machine.join("windows.json"), "[]").unwrap();
    let swept = b.call("swx_close_all_safely", json!({"reason": "draining the import"}));
    check!(
        b,
        "and it is preserved and closed like any document this bridge made",
        swept["recovered"].as_array().map(|a| !a.is_empty()).unwrap_or(false),
        s(&swept)
    );
}

/// WITH IT OFF, the translator route is used — and `linked` is false, because
/// that import really is self-contained.
#[test]
fn the_translator_route_is_used_when_interconnect_is_off() {
    let scratch = TempDir::new("interconnect-off");
    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({}));
    std::fs::write(b.scratch.join("mock-3d-interconnect-off"), "").unwrap();
    let step = a_step(&scratch, "F-W10.step");

    let r = b.call("swx_import", json!({"path": step}));
    check!(b, "the import succeeds", r["success"] == true, s(&r));
    check!(b, "the setting is reported as off", r["interconnect"] == false, s(&r));
    check!(b, "...the translator route is named", r["via"] == "LoadFile4", s(&r));
    check!(b, "...and the result is not linked to anything", r["linked"] == false, s(&r));
}

/// THE PREFERENCE IS NEVER WRITTEN — not set, and not flipped-and-restored.
///
/// A crash between the flip and the restore would leave the user's SOLIDWORKS
/// configured differently than they left it, and a CAD setting silently altered
/// by a tool is the kind of harm this bridge exists not to do. So the import
/// obeys the setting rather than arranging the world to suit itself.
#[test]
fn the_import_never_changes_the_users_preference() {
    let scratch = TempDir::new("interconnect-readonly");
    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({}));
    let step = a_step(&scratch, "F-W10.step");
    let r = b.call("swx_import", json!({"path": step}));
    check!(b, "precondition: the setting is READ and reported", r["interconnect"] == true, s(&r));

    // The setting is what it was, and a second import agrees. If the bridge had
    // flipped it to get the import through — even flipping it back afterwards —
    // this is where a crash would have left the user's SOLIDWORKS changed.
    let again = b.call("swx_import", json!({"path": a_step(&scratch, "second.step")}));
    check!(b, "the setting is unchanged after an import", again["interconnect"] == true, s(&again));

    // AND NO VERB EXISTS TO CHANGE IT. A caller cannot ask the bridge to flip
    // 3D Interconnect, by any spelling — the preference belongs to the user.
    let catalog = b.call("swx_describe", json!({}));
    let text = s(&catalog).to_lowercase();
    check!(
        b,
        "no verb offers to set a user preference",
        !text.contains("setuserpreference") && !text.contains("set_user_preference"),
        "the verb catalog mentions setting a user preference".to_string()
    );
}

/// BREAKING THE LINK IS REFUSED UNTIL IT IS MEASURED — and the refusal is useful.
///
/// Dissolving a 3D Interconnect link WRITES to the user's document, and the call
/// that does it has not been verified on this install. This project already
/// shipped one confident claim about somebody else's format from memory (pass
/// 23's OLE signature, wrong for every modern SOLIDWORKS file); that one cost a
/// refusal, and this one would modify a document.
#[test]
fn dissolving_the_link_is_refused_rather_than_guessed() {
    let scratch = TempDir::new("interconnect-dissolve");
    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({}));
    let step = a_step(&scratch, "F-W10.step");

    let before = b.call("swx_docs", json!({}));
    let r = b.call("swx_import", json!({"path": step, "dissolve": true}));
    check!(b, "it refuses", r["success"] == false, s(&r));
    check!(b, "...with a code a caller can branch on", r["errorCode"] == "dissolve_not_measured", s(&r));
    check!(
        b,
        "...saying plainly that nothing was changed",
        r["error"].as_str().unwrap_or("").contains("nothing was changed"),
        s(&r)
    );
    check!(
        b,
        "...and what would have to be measured, and the safe way round it",
        r["detail"].as_str().unwrap_or("").contains("EFFECT"),
        s(&r)
    );
    // AND IT REALLY DID NOTHING.
    let after = b.call("swx_docs", json!({}));
    check!(
        b,
        "the refusal left the session exactly as it was",
        after["count"] == before["count"],
        s(&after)
    );
}

/// THE TRANSLATOR ROUTE UNDER 3D INTERCONNECT IS THE LIVE FAILURE, and the reply
/// now carries the toggle that explains it. Before 0.8.0 a caller saw
/// "LoadFile4 opened nothing, errors=1" with no way to know why.
#[test]
fn a_translator_import_under_interconnect_reports_the_toggle_that_explains_it() {
    let scratch = TempDir::new("interconnect-explains");
    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({}));
    std::fs::write(b.scratch.join("mock-import-translator-under-interconnect"), "").unwrap();
    let step = a_step(&scratch, "F-W10.step");

    let r = b.call("swx_import", json!({"path": step}));
    check!(b, "it fails", r["success"] == false, s(&r));
    check!(
        b,
        "and the reply carries the setting that explains the failure",
        r["hostError"]["interconnect"] == true,
        s(&r)
    );
    check!(
        b,
        "...and the generic error the translator returned",
        r["hostError"]["errors"] == 1,
        s(&r)
    );
}

/// A MODAL IMPORT DIAGNOSTICS DIALOG IS NAMED, NOT ANSWERED, AND NOT A HANG.
///
/// `swImportAutoRunImportDiagnostics` is TRUE on this install (measured
/// 2026-09-17), so a STEP with faulty faces raises the Import Diagnostics dialog
/// DURING the import and the COM call never returns. A caller told only "the com
/// host did not answer" would reasonably retry — into the same dialog. A caller
/// told a dialog is on screen shows it to the user instead.
///
/// It is never answered: the choice it offers (attempt to heal the faces, or not)
/// changes the geometry the user ends up with.
#[test]
fn an_import_waiting_behind_a_dialog_says_so_instead_of_looking_hung() {
    let machine = TempDir::new("interconnect-dialog-machine");
    std::fs::write(
        machine.join("dialog-open.json"),
        json!({"hwnd": 9300, "className": "#32770", "title": "Import Diagnostics",
               "visible": true})
            .to_string(),
    )
    .unwrap();
    let scratch = TempDir::new("interconnect-dialog");
    let b = Bridge::start_in(
        TempDir::borrowed(&scratch.0),
        &[
            ("SWX_LAUNCH_FAKE_DIR", &machine.path()),
            ("SWX_MOCK_DOC_LATENCY_MS", "0"),
        ],
    );
    b.call("swx_start", json!({}));
    // The import wedges, exactly as it does behind a modal dialog.
    std::fs::write(b.scratch.join("mock-import-timeout-after-open"), "").unwrap();
    let step = a_step(&scratch, "faulty.step");

    let r = b.call("swx_import", json!({"path": step}));
    check!(b, "the import does not claim success", r["success"] != true, s(&r));
    check!(b, "it names the dialog it is waiting behind", r["stoppedByDialog"].is_object(), s(&r));
    check!(
        b,
        "...identifying it as the Import Diagnostics prompt",
        s(&r).contains("Import Diagnostics"),
        s(&r)
    );
    check!(
        b,
        "...and says this bridge never answers one",
        r["_hint"].as_str().unwrap_or("").contains("NEVER answers"),
        s(&r)
    );
}

/// THE MEASUREMENT SURFACE: a named route, exact arguments, and the invocation
/// echoed back.
///
/// No route is known to work on this edition today. `LoadFile4` executes and
/// returns nothing (`errors=1`); `LoadFile2` returns false, still unexplained;
/// `OpenDoc6(path, swDocPART, 1, …)` returns nothing with `errors=2097152`
/// (`swFileRequiresRepairError`) on two real STEP files. PowerShell cannot be
/// used to try alternatives — SOLIDWORKS Connected does not expose the type info
/// its COM adapter needs, so every member call fails `TYPE_E_ELEMENTNOTFOUND`
/// (0x8002802B) — which makes raw IDispatch through this bridge the only
/// instrument that exists. So the bridge grew a measurement surface, inside the
/// verb that already owns and registers what it opens, rather than an escape
/// hatch that would not.
#[test]
fn every_import_echoes_the_exact_invocation_it_made() {
    let scratch = TempDir::new("interconnect-matrix");
    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({}));
    let step = a_step(&scratch, "F-W10.step");

    // OPENDOC6 with explicit options and document type.
    let r = b.call(
        "swx_import",
        json!({"path": step, "route": "opendoc6", "openOptions": 3, "docType": 2}),
    );
    check!(b, "the named route is taken", r["via"] == "OpenDoc6 (3D Interconnect)", s(&r));
    check!(b, "...and the call is echoed exactly", r["invocation"]["method"] == "OpenDoc6", s(&r));
    check!(
        b,
        "...including the arguments a matrix row has to state",
        r["invocation"]["args"]["options"] == 3 && r["invocation"]["args"]["docType"] == 2,
        s(&r["invocation"])
    );

    // LOADFILE4 with a different arg string and real import data requested.
    let r = b.call(
        "swx_import",
        json!({"path": a_step(&scratch, "b.step"), "route": "loadfile4",
               "argString": "", "withImportData": true}),
    );
    check!(b, "the translator route is taken by name", r["via"] == "LoadFile4", s(&r));
    check!(b, "...with the argument string used", r["invocation"]["args"]["argString"] == "", s(&r));
    check!(
        b,
        "...and says which importData was passed",
        r["invocation"]["args"]["importData"] == "GetImportFileData(path)",
        s(&r["invocation"])
    );

    // LOADFILE2 alone, so its answer is its own rather than a fallback's.
    let r = b.call("swx_import", json!({"path": a_step(&scratch, "c.step"), "route": "loadfile2"}));
    check!(b, "LoadFile2 can be asked for by name", r["invocation"]["method"] == "LoadFile2", s(&r));

    // AND A TYPO IS REFUSED, never quietly treated as `auto` — a run that
    // measures something other than what was asked is worse than no run.
    let r = b.call("swx_import", json!({"path": a_step(&scratch, "d.step"), "route": "opendoc"}));
    check!(
        b,
        "an unknown route is refused",
        r["success"] == false && r["errorCode"] == "unknown_import_route",
        s(&r)
    );
}

/// DISSOLVE IS REFUSED ON EVERY ROUTE, not just the one it was written next to.
///
/// The refusal used to live inside the interconnect branch, so asking for
/// `dissolve:true` while the toggle was OFF — or while it could not be READ — was
/// silently dropped: the caller asked for an unlinked import, got an import, and
/// was told nothing either way about the link they asked to break. A request this
/// bridge will not honour has to fail closed wherever it arrives.
#[test]
fn dissolve_is_refused_on_every_route() {
    for (label, faults) in [
        ("interconnect on", vec![]),
        ("interconnect off", vec!["mock-3d-interconnect-off"]),
        ("toggle unreadable", vec!["mock-interconnect-toggle-unreadable"]),
    ] {
        let scratch = TempDir::new("interconnect-dissolve-routes");
        let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
        b.call("swx_start", json!({}));
        for f in &faults {
            std::fs::write(b.scratch.join(f), "").unwrap();
        }
        let step = a_step(&scratch, "F-W10.step");
        let before = b.call("swx_docs", json!({}));

        let r = b.call("swx_import", json!({"path": step, "dissolve": true}));
        // A REFUSAL, not a mention. "the reply contains the word dissolve" is
        // satisfied by a hint that happens to name it while the import went
        // ahead — which is the exact failure being tested for.
        check!(
            b,
            "dissolve is REFUSED with its code",
            r["success"] == false && r["errorCode"] == "dissolve_not_measured",
            format!("[{label}] {}", s(&r))
        );
        // AND NOTHING WAS IMPORTED: failing closed means the caller does not get
        // a linked part they did not ask for.
        let after = b.call("swx_docs", json!({}));
        check!(
            b,
            "...and nothing was opened",
            after["count"] == before["count"],
            format!("[{label}] {}", s(&after))
        );
    }
}

/// AN UNREADABLE TOGGLE IS ITS OWN OUTCOME — not "off".
///
/// `null` means SOLIDWORKS would not answer; `false` means the user turned it
/// off. They take the same route and mean opposite things about how much to trust
/// a failure: with the setting ON, nothing imports on this edition, so a failure
/// under an unreadable toggle cannot be attributed to a missing translator.
#[test]
fn an_unreadable_toggle_is_reported_as_unknown_not_as_off() {
    let scratch = TempDir::new("interconnect-unreadable");
    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({}));
    std::fs::write(b.scratch.join("mock-interconnect-toggle-unreadable"), "").unwrap();
    let step = a_step(&scratch, "F-W10.step");

    let ok = b.call("swx_import", json!({"path": step}));
    check!(b, "the import still runs", ok["success"] == true, s(&ok));
    check!(b, "the setting is reported as UNKNOWN, not false", ok["interconnect"].is_null(), s(&ok));
    check!(
        b,
        "...and the reply says the value could not be read",
        ok["interconnectSettingReadable"] == false,
        s(&ok)
    );

    // AND A FAILURE UNDER AN UNREADABLE TOGGLE IS ITS OWN ERROR.
    let scratch2 = TempDir::new("interconnect-unreadable-fail");
    let b2 = Bridge::start_in(TempDir::borrowed(&scratch2.0), &[]);
    b2.call("swx_start", json!({}));
    std::fs::write(b2.scratch.join("mock-interconnect-toggle-unreadable"), "").unwrap();
    std::fs::write(b2.scratch.join("mock-interconnect-blocks-import"), "").unwrap();
    let r = b2.call("swx_import", json!({"path": a_step(&scratch2, "x.step")}));
    check!(
        b2,
        "a failure under an unreadable toggle is NOT reported as a translator problem",
        r["errorCode"] == "interconnect_setting_unreadable",
        s(&r)
    );
    check!(
        b2,
        "...and points at the setting to check",
        r["detail"].as_str().unwrap_or("").contains("Enable 3D"),
        s(&r)
    );
}