From bfb370dd0a4be05f966decad13c610b2cff60681 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 07:00:39 +0000 Subject: [PATCH 1/3] Start fix for #1089 (socket-cli config, dry run) Assisted-by: Claude Code:claude-opus-5-5 From 898d131bc949004c3ff1162be9be34026e05a58b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 07:13:25 +0000 Subject: [PATCH 2/3] CI: fold last top-level covgap test binaries Two coverage-gap files still built as their own test binaries, one of them with a copy of another suite's private harness. They now live in their command's suite, so CI links two fewer test binaries and a change to the config fallback harness lands in one place. - covgap_utils_socket_cli_config.rs is gone. Its SOCKET_DEBUG provenance check joins config_token_and_api_base_url_authenticate in cli_config_fallback.rs, which already builds the same fixture, and now covers human and --json runs. - coverage_fix_scan_hosted_dryrun_vendored.rs moves into the scan suite as scan/hosted_dryrun_vendored.rs, on the suite's shared common and vlt_hosted_common modules. - The scan suite mounts prebuilt_common once instead of three times, so its selftests run once. - scripts/ci-test-durations.json drops the two targets and adds their time to scan and cli_config_fallback. Refs #1089 Assisted-by: Claude Code:claude-opus-5-5 --- .../tests/cli_config_fallback.rs | 27 ++- .../tests/covgap_utils_socket_cli_config.rs | 157 ------------------ .../scan/covgap_commands_scan_vendor_flow.rs | 3 +- .../hosted_dryrun_vendored.rs} | 17 +- crates/socket-patch-cli/tests/scan/main.rs | 4 + .../tests/scan/scan_nested_project_e2e.rs | 3 +- scripts/ci-test-durations.json | 6 +- 7 files changed, 40 insertions(+), 177 deletions(-) delete mode 100644 crates/socket-patch-cli/tests/covgap_utils_socket_cli_config.rs rename crates/socket-patch-cli/tests/{coverage_fix_scan_hosted_dryrun_vendored.rs => scan/hosted_dryrun_vendored.rs} (99%) diff --git a/crates/socket-patch-cli/tests/cli_config_fallback.rs b/crates/socket-patch-cli/tests/cli_config_fallback.rs index bc7d78b81..0a49807a0 100644 --- a/crates/socket-patch-cli/tests/cli_config_fallback.rs +++ b/crates/socket-patch-cli/tests/cli_config_fallback.rs @@ -133,7 +133,9 @@ const PROXY_NOTICE: &str = "No SOCKET_API_TOKEN set"; /// A config-supplied token + apiBaseUrl authenticate the client: with no /// org anywhere the binary must hit the fixture's `/v0/organizations` with -/// the config token as bearer. +/// the config token as bearer. The `SOCKET_DEBUG` provenance line for the +/// config-sourced URL (the `inspect` closure in `resolve_api_base_url`) +/// prints only under the debug flag, in human and `--json` runs alike. #[tokio::test] async fn config_token_and_api_base_url_authenticate() { let server = MockServer::start().await; @@ -158,6 +160,29 @@ async fn config_token_and_api_base_url_authenticate() { Some(format!("Bearer {}", token('c')).as_str()), "org auto-resolve must hit the config apiBaseUrl with the config token" ); + assert!( + !out.stderr.contains("api base url:"), + "without SOCKET_DEBUG the provenance diagnostic must stay silent; \ + stderr:\n{}", + out.stderr + ); + + let provenance = format!("api base url: `{}` from socket-cli config", server.uri()); + for json in [false, true] { + let mut cmd = scan_cmd(project.path(), data.path()); + cmd.env("SOCKET_DEBUG", "1"); + if json { + cmd.arg("--json"); + } + let out = run(cmd); + assert_eq!(out.code, Some(0), "json={json}; stderr:\n{}", out.stderr); + assert!( + out.stderr.contains(&provenance), + "SOCKET_DEBUG=1 must name the config apiBaseUrl and its source \ + (json={json}); stderr:\n{}", + out.stderr + ); + } } /// `defaultOrg` from the config skips the org auto-resolve round-trip — diff --git a/crates/socket-patch-cli/tests/covgap_utils_socket_cli_config.rs b/crates/socket-patch-cli/tests/covgap_utils_socket_cli_config.rs deleted file mode 100644 index 15a6ec773..000000000 --- a/crates/socket-patch-cli/tests/covgap_utils_socket_cli_config.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! Coverage-gap e2e for `socket_patch_core::utils::socket_cli_config`. -//! -//! Targets the one uncovered prod line in that module: the `SOCKET_DEBUG` -//! provenance diagnostic printed when `resolve_api_base_url` sources -//! `apiBaseUrl` from the socket-cli config file (the `inspect` closure in -//! `resolve_api_base_url`). It has to be exercised at the binary layer: -//! in-crate it would require `load()`, whose `OnceLock` cache is -//! process-global and would poison sibling lib tests. -//! -//! Harness cloned from `cli_config_fallback.rs` (helpers there are private -//! to that suite, so the minimal subset is reproduced here): hermetic -//! `SOCKET_*` scrub, data dir pointed at a tempdir fixture, empty npm -//! project so scan makes exactly the org auto-resolve round-trip. - -use std::path::Path; -use std::process::Command; - -use base64::Engine as _; -use wiremock::matchers::{method, path}; -use wiremock::{Mock, MockServer, ResponseTemplate}; - -#[path = "common/hermetic.rs"] -mod hermetic; - -/// The platform env var that positions the socket-cli data dir. -const DATA_DIR_VAR: &str = if cfg!(windows) { - "LOCALAPPDATA" -} else { - "XDG_DATA_HOME" -}; - -/// A shape-valid API token (`sktsec_<44 chars>_api`) so the token-shape -/// warning never muddies stderr assertions. -fn token(filler: char) -> String { - format!("sktsec_{}_api", filler.to_string().repeat(44)) -} - -/// Write a socket-cli `config.json` fixture (base64-encoded, as the real -/// tool persists it) under `data_dir/socket/settings/`. -fn write_config(data_dir: &Path, json: &serde_json::Value) { - let dir = data_dir.join("socket").join("settings"); - std::fs::create_dir_all(&dir).unwrap(); - let encoded = base64::engine::general_purpose::STANDARD.encode(json.to_string()); - std::fs::write(dir.join("config.json"), encoded).unwrap(); -} - -/// Build a hermetic `socket-patch scan --json -e npm` command: the shared -/// hermetic env (every ambient `SOCKET_*` scrubbed, telemetry off) with the -/// config layer re-enabled by an explicit falsy `SOCKET_NO_CONFIG`, and -/// the data dir pointed at `data_dir`. -fn scan_cmd(project: &Path, data_dir: &Path) -> Command { - let mut cmd = hermetic::binary_command(); - cmd.args(["scan", "--json", "-e", "npm", "--cwd"]) - .arg(project); - cmd.env_remove("VIRTUAL_ENV"); - cmd.env(DATA_DIR_VAR, data_dir); - cmd.env("SOCKET_NO_CONFIG", "0"); - cmd -} - -/// Empty npm project: a lone package.json with no dependencies, so the -/// crawler discovers zero packages and scan exits 0 without a batch POST. -fn write_empty_project(root: &Path) { - std::fs::write( - root.join("package.json"), - r#"{ "name": "covgap-config-debug-fixture", "version": "0.0.0" }"#, - ) - .unwrap(); -} - -/// Mock `GET /v0/organizations` (the org auto-resolve round-trip that fires -/// on authenticated client construction when no org slug is configured). -async fn mock_organizations(server: &MockServer) { - Mock::given(method("GET")) - .and(path("/v0/organizations")) - .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ - "organizations": { - "org-1": { - "id": "org-1", - "name": "Covgap Fixture Org", - "image": null, - "plan": "free", - "slug": "covgap-fixture-org" - } - } - }))) - .mount(server) - .await; -} - -struct RunOutput { - code: Option, - stderr: String, -} - -fn run(mut cmd: Command) -> RunOutput { - let out = cmd.output().expect("run socket-patch"); - RunOutput { - code: out.status.code(), - stderr: String::from_utf8_lossy(&out.stderr).into_owned(), - } -} - -/// With `SOCKET_DEBUG=1` and `apiBaseUrl` resolved from the socket-cli -/// config (no `SOCKET_API_URL` in the env — the scrub in `scan_cmd` -/// guarantees the env layer misses), the provenance diagnostic names the -/// chosen URL and its source on stderr; without the debug flag the same -/// run stays silent about it. Covers the `inspect` closure in -/// `resolve_api_base_url` (`utils/socket_cli_config.rs`). -#[tokio::test] -async fn debug_names_config_api_base_url_provenance() { - let server = MockServer::start().await; - mock_organizations(&server).await; - let data = tempfile::tempdir().unwrap(); - let project = tempfile::tempdir().unwrap(); - write_empty_project(project.path()); - write_config( - data.path(), - &serde_json::json!({ "apiToken": token('c'), "apiBaseUrl": server.uri() }), - ); - - let provenance = format!("api base url: `{}` from socket-cli config", server.uri()); - - // Debug on: the provenance line must appear, and the run still works - // end-to-end (the config URL really was chosen — the fixture got hit). - let mut cmd = scan_cmd(project.path(), data.path()); - cmd.env("SOCKET_DEBUG", "1"); - let out = run(cmd); - assert_eq!(out.code, Some(0), "stderr:\n{}", out.stderr); - assert!( - out.stderr.contains(&provenance), - "SOCKET_DEBUG=1 must print the apiBaseUrl provenance line \ - ({provenance:?}); stderr:\n{}", - out.stderr - ); - assert!( - server - .received_requests() - .await - .unwrap_or_default() - .iter() - .any(|r| r.url.path() == "/v0/organizations"), - "control: the config apiBaseUrl must actually be used, or the \ - provenance line under test is asserting a lie" - ); - - // Debug off (scrubbed env): the very same run prints no provenance — - // the diagnostic is gated on SOCKET_DEBUG, not on the config source. - let out = run(scan_cmd(project.path(), data.path())); - assert_eq!(out.code, Some(0), "stderr:\n{}", out.stderr); - assert!( - !out.stderr.contains("api base url:"), - "without SOCKET_DEBUG the provenance diagnostic must stay silent; \ - stderr:\n{}", - out.stderr - ); -} diff --git a/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs b/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs index eb9352a13..0aa90d674 100644 --- a/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs +++ b/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs @@ -2,8 +2,7 @@ use crate::common::{binary, git_sha256, hermetic_command}; -#[path = "../prebuilt_common/mod.rs"] -mod prebuilt_common; +use crate::prebuilt_common; use std::fs::OpenOptions; use std::path::Path; diff --git a/crates/socket-patch-cli/tests/coverage_fix_scan_hosted_dryrun_vendored.rs b/crates/socket-patch-cli/tests/scan/hosted_dryrun_vendored.rs similarity index 99% rename from crates/socket-patch-cli/tests/coverage_fix_scan_hosted_dryrun_vendored.rs rename to crates/socket-patch-cli/tests/scan/hosted_dryrun_vendored.rs index b530d7569..3883891dd 100644 --- a/crates/socket-patch-cli/tests/coverage_fix_scan_hosted_dryrun_vendored.rs +++ b/crates/socket-patch-cli/tests/scan/hosted_dryrun_vendored.rs @@ -10,18 +10,13 @@ //! shapes) produces the vendored lock + `.socket/vendor/state.json` entry; //! the hosted API is wiremock (`in_process_redirect_pnpm.rs` shapes). -#[path = "common/envelope.rs"] -mod envelope; -#[path = "common/hermetic.rs"] -mod hermetic; -#[path = "prebuilt_common/mod.rs"] -mod prebuilt_common; -use envelope::codes_in; +use crate::prebuilt_common; use std::path::Path; -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; +use crate::common::envelope::codes_in; +use crate::common::hermetic; +use crate::vlt_hosted_common; use serde_json::{json, Value}; use serial_test::serial; @@ -406,8 +401,8 @@ async fn dry_run_over_vendored_project_previews_the_wet_takeover() { "the dry-run must preview the wet outcome: {doc:#}" ); assert_eq!( - Some(&hosted_skipped(&doc)).map(Vec::len), - Some(0), + hosted_skipped(&doc).len(), + 0, "a revertable vendored purl is not skipped: {doc:#}" ); // Review finding: the pnpm trustLockfile auto-config the wet run diff --git a/crates/socket-patch-cli/tests/scan/main.rs b/crates/socket-patch-cli/tests/scan/main.rs index 87752dd62..edd649539 100644 --- a/crates/socket-patch-cli/tests/scan/main.rs +++ b/crates/socket-patch-cli/tests/scan/main.rs @@ -1,11 +1,14 @@ //! `scan`: batching, ecosystem scope, ordering, paths, sync, hosted refusals, //! the scan and hosted `--json` failure paths and their human render checks, +//! the hosted dry run over a vendored project, //! scan on a real terminal, and the vendor flow. //! //! One test binary per command: each module was its own binary. #[path = "../common/mod.rs"] mod common; +#[path = "../prebuilt_common/mod.rs"] +mod prebuilt_common; #[path = "../common/pty_io.rs"] mod pty_io; #[path = "../common/rollback_json.rs"] @@ -19,6 +22,7 @@ mod coverage_fix_scan_discovery_corrupt_ledger; mod covgap_commands_fetch_stage; mod covgap_commands_scan_vendor_flow; mod covgap_ecosystem_dispatch; +mod hosted_dryrun_vendored; mod hosted_failure_paths; mod hosted_fixture; mod hosted_management_refusals; diff --git a/crates/socket-patch-cli/tests/scan/scan_nested_project_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_nested_project_e2e.rs index f942fe749..664c39483 100644 --- a/crates/socket-patch-cli/tests/scan/scan_nested_project_e2e.rs +++ b/crates/socket-patch-cli/tests/scan/scan_nested_project_e2e.rs @@ -9,8 +9,7 @@ use crate::common::envelope::{all_codes, parse_json_envelope}; use crate::common::{binary, git_sha256, hermetic_command}; -#[path = "../prebuilt_common/mod.rs"] -mod prebuilt_common; +use crate::prebuilt_common; use std::path::Path; diff --git a/scripts/ci-test-durations.json b/scripts/ci-test-durations.json index b33f5ed90..c5c695740 100644 --- a/scripts/ci-test-durations.json +++ b/scripts/ci-test-durations.json @@ -17,7 +17,7 @@ "cli": 7.69, "cli_apply_silent": 0.1, "cli_argv_non_utf8": 0.0, - "cli_config_fallback": 0.18, + "cli_config_fallback": 0.26, "cli_get_silent": 0.06, "cli_global_args": 0.36, "cli_parse_apply": 0.05, @@ -36,14 +36,12 @@ "cli_sigpipe": 0.0, "command_module_layering": 0.11, "contract_gradle_codes": 0.93, - "coverage_fix_scan_hosted_dryrun_vendored": 1.61, "covgap_api_blob_fetcher": 4.04, "covgap_commands_vex": 0.2, "covgap_crawlers_composer_crawler": 0.01, "covgap_crawlers_npm_crawler": 0.04, "covgap_patch_apply": 0.06, "covgap_update_state": 0.01, - "covgap_utils_socket_cli_config": 0.08, "crawl_fd_limit_e2e": 0.0, "crawler_cargo_e2e": 0.06, "crawler_composer_e2e": 0.11, @@ -216,7 +214,7 @@ "repair": 10.03, "rollback": 6.61, "rollback_new_file_e2e": 0.01, - "scan": 12.35, + "scan": 13.96, "scan_api_retry_e2e": 2.42, "scan_pnpm_relocated_store_cwd_e2e": 0.07, "scan_requirements_lock_only": 2.68, From 777911b20dac392683ce7520b3d68a3d4e1eba90 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 07:26:41 +0000 Subject: [PATCH 3/3] Reuse scan suite helpers in hosted dry run tests Review follow-ups for the fold into the scan suite: - The hosted dry-run tests use the suite's shared hosted_skipped, warning_detail and envelope::hosted_pins instead of private copies, and the vlt test builds its vendored project with the shared vlt_vendored::vendored_project fixture. - Drop #[serial]: the tests only spawn hermetic children in their own tempdirs against their own mock servers, so they can run in parallel with the rest of the binary. - The config fallback test checks that the SOCKET_DEBUG provenance line stays silent without the flag in --json runs too, as the deleted covgap test did. Refs #1089 Assisted-by: Claude Code:claude-opus-5-5 --- .../tests/cli_config_fallback.rs | 33 ++++--- .../tests/scan/hosted_dryrun_vendored.rs | 92 ++----------------- 2 files changed, 27 insertions(+), 98 deletions(-) diff --git a/crates/socket-patch-cli/tests/cli_config_fallback.rs b/crates/socket-patch-cli/tests/cli_config_fallback.rs index 0a49807a0..bccfb4f5e 100644 --- a/crates/socket-patch-cli/tests/cli_config_fallback.rs +++ b/crates/socket-patch-cli/tests/cli_config_fallback.rs @@ -160,28 +160,33 @@ async fn config_token_and_api_base_url_authenticate() { Some(format!("Bearer {}", token('c')).as_str()), "org auto-resolve must hit the config apiBaseUrl with the config token" ); - assert!( - !out.stderr.contains("api base url:"), - "without SOCKET_DEBUG the provenance diagnostic must stay silent; \ - stderr:\n{}", - out.stderr - ); let provenance = format!("api base url: `{}` from socket-cli config", server.uri()); - for json in [false, true] { + for (json, debug) in [(false, false), (true, false), (false, true), (true, true)] { let mut cmd = scan_cmd(project.path(), data.path()); - cmd.env("SOCKET_DEBUG", "1"); + if debug { + cmd.env("SOCKET_DEBUG", "1"); + } if json { cmd.arg("--json"); } let out = run(cmd); assert_eq!(out.code, Some(0), "json={json}; stderr:\n{}", out.stderr); - assert!( - out.stderr.contains(&provenance), - "SOCKET_DEBUG=1 must name the config apiBaseUrl and its source \ - (json={json}); stderr:\n{}", - out.stderr - ); + if debug { + assert!( + out.stderr.contains(&provenance), + "SOCKET_DEBUG=1 must name the config apiBaseUrl and its source \ + (json={json}); stderr:\n{}", + out.stderr + ); + } else { + assert!( + !out.stderr.contains("api base url:"), + "without SOCKET_DEBUG the provenance diagnostic must stay silent \ + (json={json}); stderr:\n{}", + out.stderr + ); + } } } diff --git a/crates/socket-patch-cli/tests/scan/hosted_dryrun_vendored.rs b/crates/socket-patch-cli/tests/scan/hosted_dryrun_vendored.rs index 3883891dd..17cf80c87 100644 --- a/crates/socket-patch-cli/tests/scan/hosted_dryrun_vendored.rs +++ b/crates/socket-patch-cli/tests/scan/hosted_dryrun_vendored.rs @@ -14,45 +14,19 @@ use crate::prebuilt_common; use std::path::Path; -use crate::common::envelope::codes_in; +use crate::common::envelope::{codes_in, hosted_pins}; use crate::common::hermetic; +use crate::hosted_fixture::{hosted_skipped, warning_detail}; use crate::vlt_hosted_common; use serde_json::{json, Value}; -use serial_test::serial; use socket_patch_core::hash::git_sha256::compute_git_sha256_from_bytes; use wiremock::matchers::{method, path, path_regex}; use wiremock::{Mock, MockServer, ResponseTemplate}; -/// The hosted `skipped` events as the pre-v5.0 `redirect.skipped[]` rows -/// (`{purl, uuid, reason: , detail?: }`). -fn hosted_skipped(doc: &Value) -> Vec { - doc["events"] - .as_array() - .into_iter() - .flatten() - .filter(|e| e["details"]["mode"] == "hosted" && e["action"] == "skipped") - .map(|e| { - let mut row = json!({"purl": e["purl"], "uuid": e["uuid"], "reason": e["errorCode"]}); - if e["reason"].is_string() { - row["detail"] = e["reason"].clone(); - } - row - }) - .collect() -} - /// How many pins a hosted run wrote (`applied`) or would write (`verified`). -fn hosted_pinned(doc: &Value) -> u64 { - doc["events"] - .as_array() - .into_iter() - .flatten() - .filter(|e| { - e["details"]["mode"] == "hosted" - && (e["action"] == "applied" || e["action"] == "verified") - }) - .count() as u64 +fn hosted_pinned(doc: &Value) -> usize { + hosted_pins(doc).len() } const ORG: &str = "test-org"; @@ -338,14 +312,6 @@ fn tree_snapshot(root: &Path) -> std::collections::BTreeMap> { out } -fn warning_detail<'a>(doc: &'a Value, code: &str) -> Option<&'a str> { - doc["warnings"] - .as_array()? - .iter() - .find(|w| w["code"] == code) - .and_then(|w| w["detail"].as_str()) -} - fn rewritten_files(doc: &Value) -> Vec<&str> { doc["redirect"]["rewrittenFiles"] .as_array() @@ -360,7 +326,6 @@ fn rewritten_files(doc: &Value) -> Vec<&str> { /// (`redirect_pnpm_entry_vendored`, "run `vendor --revert` first") never /// appears — the run itself performs that revert. Nothing lands on disk. #[tokio::test] -#[serial] async fn dry_run_over_vendored_project_previews_the_wet_takeover() { let server = MockServer::start().await; mock_hosted_api(&server).await; @@ -407,8 +372,7 @@ async fn dry_run_over_vendored_project_previews_the_wet_takeover() { ); // Review finding: the pnpm trustLockfile auto-config the wet run // writes (the takeover splices the root v9 lock) must be previewed too. - let trust = warning_detail(&doc, "redirect_pnpm_trust_lockfile") - .unwrap_or_else(|| panic!("the trust config must be previewed: {doc:#}")); + let trust = warning_detail(&doc, "redirect_pnpm_trust_lockfile"); // The vendor run created pnpm-workspace.yaml for its own wiring, and // the takeover's revert removes it again, so the wet run writes a new // one: the preview plans against the same reverted project. @@ -465,8 +429,7 @@ async fn dry_run_over_vendored_project_previews_the_wet_takeover() { workspace(root).is_some_and(|w| w.contains("trustLockfile: true")), "the wet run writes what the preview promised: {wet:#}" ); - let wet_trust = warning_detail(&wet, "redirect_pnpm_trust_lockfile") - .unwrap_or_else(|| panic!("the wet run reports the trust config: {wet:#}")); + let wet_trust = warning_detail(&wet, "redirect_pnpm_trust_lockfile"); assert!( wet_trust.contains("to a new pnpm-workspace.yaml"), "the preview named the wet run's file: {wet_trust}" @@ -480,7 +443,6 @@ async fn dry_run_over_vendored_project_previews_the_wet_takeover() { /// clean takeover, and never handed to the rewriters for a second, /// contradictory diagnosis. #[tokio::test] -#[serial] async fn dry_run_refuses_unrevertable_vendored_state_like_the_wet_run() { let server = MockServer::start().await; mock_hosted_api(&server).await; @@ -567,7 +529,6 @@ fn summary_line(stdout: &str) -> &str { /// package.json, so the deleted vendored ledger entry and artifact and the /// reverted wiring are committed too. #[tokio::test] -#[serial] async fn human_takeover_prints_migration_lines_and_matching_file_counts() { let server = MockServer::start().await; mock_hosted_api(&server).await; @@ -661,7 +622,6 @@ fn write_package_lock_project(root: &Path) { /// `allow-remote=all` write the wet run makes was neither warned about /// ("would be written to a new project .npmrc") nor listed in `rewrittenFiles`. #[tokio::test] -#[serial] async fn dry_run_package_lock_takeover_previews_the_npmrc_write() { let server = MockServer::start().await; mock_hosted_api(&server).await; @@ -686,8 +646,7 @@ async fn dry_run_package_lock_takeover_previews_the_npmrc_write() { "{doc:#}" ); assert_eq!(hosted_pinned(&doc), 1, "{doc:#}"); - let detail = warning_detail(&doc, "redirect_npm_allow_remote") - .unwrap_or_else(|| panic!("the .npmrc write must be previewed: {doc:#}")); + let detail = warning_detail(&doc, "redirect_npm_allow_remote"); assert!( detail.contains("would be written to a new project .npmrc") && detail.contains("patch.test"), @@ -715,48 +674,13 @@ async fn dry_run_package_lock_takeover_previews_the_npmrc_write() { /// `redirected: 1` (never `redirect_vlt_entry_vendored`) and writes /// nothing; the wet run lands the same outcome. #[tokio::test] -#[serial] async fn vlt_dry_run_over_vendored_project_previews_the_wet_takeover() { use vlt_hosted_common as hosted; let server = MockServer::start().await; hosted::mock_all(&server).await; let tmp = tempfile::tempdir().unwrap(); let root = tmp.path(); - let registry_lock = format!( - "{{\n \"lockfileVersion\": 1,\n \"options\": {{}},\n \"nodes\": {{\n {}\n }},\n \ - \"edges\": {{\n \"file~_d left-pad\": \"prod 1.3.0 {}\"\n }}\n}}\n", - hosted::registry_node(hosted::TILDE_ID), - hosted::TILDE_ID - ); - std::fs::write(root.join("vlt-lock.json"), ®istry_lock).unwrap(); - hosted::write_package_json(root); - hosted::install_importer(root, hosted::PRISTINE); - let socket = root.join(".socket"); - std::fs::create_dir_all(socket.join("blobs")).unwrap(); - let mut record = hosted::view_body(); - record.as_object_mut().unwrap().remove("purl"); - record["exportedAt"] = json!("2026-01-01T00:00:00Z"); - std::fs::write( - socket.join("manifest.json"), - json!({ "patches": { hosted::PURL: record } }).to_string(), - ) - .unwrap(); - std::fs::write( - socket - .join("blobs") - .join(hosted::git_sha256(hosted::PATCHED)), - hosted::PATCHED, - ) - .unwrap(); - let cwd = root.to_str().unwrap().to_string(); - let fixture = prebuilt_common::Server::project(root); - let (code, env, stderr) = hosted::run_json( - root, - &["vendor", "--cwd", &cwd], - &[("SOCKET_VENDOR_URL", &fixture.uri)], - ); - assert_eq!(code, 0, "{env:#}\n{stderr}"); - std::fs::remove_file(socket.join("manifest.json")).unwrap(); + crate::vlt_vendored::vendored_project(root, false); let vendored_lock = std::fs::read(root.join("vlt-lock.json")).unwrap(); let vendored_state = std::fs::read(root.join(".socket/vendor/state.json")).unwrap(); let vendored_pkg = std::fs::read(root.join("package.json")).unwrap();