@@ -235,6 +235,49 @@ exit 0
235235 )
236236}
237237
238+ /// Driver script for the `npm install -g` variant. Installs minimist
239+ /// globally (into `$(npm root -g)`), runs scan + apply with `--global`,
240+ /// and verifies the marker landed in the global node_modules tree.
241+ fn make_global_script ( api_url : & str ) -> String {
242+ format ! (
243+ r#"#!/usr/bin/env bash
244+ set -uo pipefail
245+ COMMON_ARGS=(--api-url '{api_url}' --api-token fake --org {ORG})
246+
247+ # Global install — populates $(npm root -g)/minimist/.
248+ npm install -g --silent --no-audit --no-fund minimist@1.2.2 > /tmp/install.log 2>&1 || {{
249+ cat /tmp/install.log >&2; exit 1
250+ }}
251+
252+ NPM_GLOBAL_ROOT=$(npm root -g)
253+ GLOBAL_FILE="$NPM_GLOBAL_ROOT/minimist/index.js"
254+ [ -f "$GLOBAL_FILE" ] || {{ echo "FAIL: $GLOBAL_FILE missing" >&2; ls "$NPM_GLOBAL_ROOT" >&2 || true; exit 1; }}
255+ echo "Global-installed at: $GLOBAL_FILE" >&2
256+
257+ # scan + apply run from an empty workspace; --global tells the crawler
258+ # to look at $(npm root -g) instead of cwd-relative node_modules.
259+ mkdir -p /workspace/proj && cd /workspace/proj
260+
261+ socket-patch scan --json --sync --yes --global "${{COMMON_ARGS[@]}}" \
262+ --ecosystems npm 2>/tmp/sync.err
263+ cat /tmp/sync.err >&2
264+
265+ socket-patch apply --json --force --offline --global --ecosystems npm 2>/tmp/apply.err
266+ cat /tmp/apply.err >&2
267+
268+ if ! grep -q 'SOCKET-PATCH-E2E-MARKER' "$GLOBAL_FILE"; then
269+ echo "FAIL: marker not in $GLOBAL_FILE" >&2
270+ head -3 "$GLOBAL_FILE" >&2
271+ exit 1
272+ fi
273+
274+ echo "===PATCH VERIFIED===" >&2
275+ echo "===E2E PASS==="
276+ exit 0
277+ "#
278+ )
279+ }
280+
238281fn run_in_container ( script : & str ) -> std:: process:: Output {
239282 Command :: new ( "docker" )
240283 . args ( [
@@ -333,6 +376,31 @@ async fn npm_install_scan_apply_rollback_cycle() {
333376 ) ;
334377}
335378
379+ #[ tokio:: test]
380+ async fn npm_global_install_full_apply_chain ( ) {
381+ // PURL must be the lowercased form scan's crawler emits — see the
382+ // nuget docker test for the same constraint. (npm names are already
383+ // lowercase in practice; we use the canonical form here for clarity.)
384+ let after_hash = git_sha256 ( PATCHED_BYTES ) ;
385+ let server = make_mock_server ( & after_hash) . await ;
386+ if host_mode ( ) {
387+ // Host mode doesn't have a global npm prefix we can safely
388+ // mutate, so skip silently. Docker mode is the canonical run.
389+ return ;
390+ }
391+ assert_docker_image_present ( ) ;
392+ let api = api_url_for_container ( & server) ;
393+ let out = run_in_container ( & make_global_script ( & api) ) ;
394+ let stdout = String :: from_utf8_lossy ( & out. stdout ) ;
395+ let stderr = String :: from_utf8_lossy ( & out. stderr ) ;
396+ assert ! (
397+ out. status. success( ) ,
398+ "npm global apply failed:\n stdout=\n {stdout}\n stderr=\n {stderr}"
399+ ) ;
400+ assert ! ( stderr. contains( "===PATCH VERIFIED===" ) , "stderr=\n {stderr}" ) ;
401+ assert ! ( stdout. contains( "===E2E PASS===" ) , "stdout=\n {stdout}" ) ;
402+ }
403+
336404/// Smoke test: verify the test infrastructure starts up correctly. This
337405/// runs even without Docker so the test binary itself compiles + the
338406/// wiremock listener path works.
0 commit comments