diff --git a/CHANGELOG.md b/CHANGELOG.md index 635838ec3..2c45ee7c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2793,7 +2793,7 @@ and regression tests were added throughout (the lib + integration suites grow by ### Tests -- New `tests/telemetry_e2e.rs` end-to-end behavioral coverage: +- New `tests/cli/telemetry_e2e.rs` end-to-end behavioral coverage: apply/scan/get/list emit telemetry against a wiremock recorder; `SOCKET_OFFLINE=1` produces zero telemetry POSTs across all four; scan falls back on 401 + tags the resulting event; scan does NOT diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index 9eb0c4afb..c9af4822d 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -134,7 +134,7 @@ For a **9.0 root lock**, the CLI ensures `pnpm-workspace.yaml` carries `trustLoc `scan --sync` is sugar for `--mode agent --prune` — the canonical single-flag agent-mode bot invocation. `scan --json --sync` discovers, applies, and reconciles state in one pass. -**`scan --ecosystems` scopes the crawl (v5.0)**: without `--prune`/`--sync`, a `scan` given `--ecosystems`/`-e` runs only the named ecosystems' crawlers — everything the run counts, queries and shows (`scannedPackages`, the batch query, `packages[]`, the table, `updates[]`, `wiringLive`, the `gem_bundle_config_path_ignored` warning) was already narrowed to them, so the skipped crawls could only be filtered away. The one visible difference: `lockfileOnlyPackages` (and the human "not yet installed" note) counts only the selected ecosystems' lockfile-only entries (a skipped crawl cannot vouch for another ecosystem's uninstalled lockfile entries). A GC run (`--prune`, or `--sync`, which implies it — in every mode, hosted included) still crawls every ecosystem, because the prune judges each manifest entry against the FULL installed set (see `scan --prune` above); its output, `lockfileOnlyPackages` included, is unchanged. Without `--ecosystems` nothing changes. Pinned by `tests/scan_ecosystems_scope_e2e.rs`. +**`scan --ecosystems` scopes the crawl (v5.0)**: without `--prune`/`--sync`, a `scan` given `--ecosystems`/`-e` runs only the named ecosystems' crawlers — everything the run counts, queries and shows (`scannedPackages`, the batch query, `packages[]`, the table, `updates[]`, `wiringLive`, the `gem_bundle_config_path_ignored` warning) was already narrowed to them, so the skipped crawls could only be filtered away. The one visible difference: `lockfileOnlyPackages` (and the human "not yet installed" note) counts only the selected ecosystems' lockfile-only entries (a skipped crawl cannot vouch for another ecosystem's uninstalled lockfile entries). A GC run (`--prune`, or `--sync`, which implies it — in every mode, hosted included) still crawls every ecosystem, because the prune judges each manifest entry against the FULL installed set (see `scan --prune` above); its output, `lockfileOnlyPackages` included, is unchanged. Without `--ecosystems` nothing changes. Pinned by `tests/scan/scan_ecosystems_scope_e2e.rs`. **Path-scoped scans (`scan [PATHS]...`, v5.0)**: what a PATH means depends on the mode. @@ -161,9 +161,9 @@ The rewriter reads a fixed set of candidate files from the project root: the npm **get --mode and installed narrowing (v3.6).** `get --mode hosted|vendored` consumes the resolved patch(es) through the SAME engines as `scan --mode hosted|vendored`, so for the same selected (purl, uuid) set the on-disk result is identical by construction — the per-advisory selector for hosted/vendored (`get --save-only` then `vendor` still works). **Agent mode (v5.0 lock + residue rules)**: the download phase runs under `<.socket>/apply.lock` and hands the guard to the nested apply, so download → manifest write → apply is one lock window (the nested apply never re-acquires and inherits every caller flag — `--lock-timeout` and `--verbose` included); a failed acquire is `{status: "error", errorCode: "lock_held" | "lock_io", error}` on get's legacy envelope, exit 1, before any fetch (a read-only `.socket/` fails here, naming the lock path). `.socket/` and `.socket/blobs/` are created only when a record is actually persisted — an all-skipped or all-failed run leaves no `.socket/` on a fresh project — and a same-uuid `get ` re-run rewrites neither the manifest nor the blobs. Semantics: * **Hosted** (`get GHSA-… --mode hosted`): resolves the advisory, then hands the selected (purl, uuid) pairs to scan's hosted engine — reference grants, cross-mode takeover pre-revert, lockfile rewrite (no ledger, v5.0), gem stale-install probe, warnings, confirmation rules (cargo via `confirmed_cargo_uuids`, golang via `confirmed_golang_uuids` only) all identical to `scan --mode hosted`, and (v5.0) under the same `apply.lock` acquisition — taken around the first wet write, never on `--dry-run` or when nothing would be written; a failed acquire folds as top-level `errorCode: "lock_held" | "lock_io"` + string `error` (exit 1), and `--dry-run` under a held lock still exits 0. **No manifest write, no blobs, no ledger** — the lockfile edits are the persistence. JSON: get's legacy envelope gains the same nested `redirect` sub-object as scan's (`{mode:"hosted", redirected, rewrittenFiles, skipped, warnings, dryRun}`); the top-level shape is `{status, found, patches:[], warnings?}` — `downloaded`/`applied` are absent (nothing is downloaded into `.socket/`). Exit codes follow scan's hosted semantics: skipped grants and rewriter warnings never flip the exit; infra errors (reference fetch, file writes) exit 1. Human prompt: `Redirect N packages to the hosted patch server?` (singular for one; `--yes`/`--json`/non-TTY auto-accept as usual). This confirm is get's alone: `scan` never prompts. -* **Vendored** (`get GHSA-… --mode vendored`): the download phase is scan's vendored posture — **manifest-free (v5.0)**: the selected records are fetched into memory (`download_patch_records`; blobs held in memory; nothing under `.socket/` is written; the nested apply never runs), then scan's vendor step runs under the apply lock over exactly the selected records, like `scan --mode vendored` (no whole-manifest scope and no `[note]` about other records — that blast radius is retired with the manifest; a legacy manifest record for a vendored purl is migrated out of `.socket/manifest.json` the same way scan does it). JSON: get's envelope takes the detached download envelope's shape — `{status, found, downloaded, skipped, failed, detached: true, patches: [{purl, uuid, action: "downloaded" | "skipped" | "failed", …}], warnings?}` (`applied` is absent; `detached: true` is pinned; a `downloaded` record for a purl the vendor ledger holds at another uuid carries the additive `oldUuid`, derived from the ledger — the human `[fetch]` line reads ` (replacing )`) — and gains the nested `vendor` Envelope exactly like scan's `result["vendor"]`; a vendor-step error folds the partial envelope + `{status:"error", error:{code,message}}` in (a pre-failure takeover reconcile may have already mutated the ledger — its events must reach the consumer). Exit: download failures or vendor `has_errors` → `partial_failure`/1. Human prompt: `Download and vendor N patches?`; `--dry-run` prints `[dry-run] Would download and vendor N patches. No changes made.` on both identifier paths (uuid and search). Telemetry mirrors scan's vendored arms (`track_outcomes_for_vendor` / `track_patch_vendor_failed`). **Bun vendored preflight (additive)** — shared by `get --mode vendored` on both its paths and `scan --mode vendored`: before ANY patch download, and only when the selection holds a `pkg:npm/` purl, the download phase reads `bun.lock`/`bun.lockb` once (`preflight_vendor`) and, when the vendor backend would refuse the project — a malformed, unreadable or unsupported `bun.lockb` → `vendor_bun_lockb_invalid`; an unreadable `bun.lock` → `vendor_lockfile_missing`; a `lockfileVersion` other than 0/1/2 or a non-canonical `packages` grammar → `vendor_lockfile_version_unsupported`; `workspace:` packages in a lock below version 2 → `vendor_bun_workspace_unsupported` — every `pkg:npm/` result becomes `{action:"failed", errorCode:, error:}` with NO fetch (the patch view is never requested) and no patch record; other ecosystems' results are untouched. **Search path** (`get --mode vendored`) and `scan --mode vendored`: the records ride `patches[]` / `download.patches[]` with `downloaded: 0`, the download phase writes nothing under `.socket/` (v5.0 — a pre-existing `.socket/manifest.json`, including a record seeded for another purl, is left byte-untouched), the vendor step still runs over the remaining records (no event for the refused purl), exit `partial_failure`/1. **uuid path** (`get --mode vendored`): the uuid lookup is the only fetch; the run exits 1 BEFORE the vendor step with exactly `{status:"error", found:1, downloaded:0, skipped:0, failed:1, error:{code, message}, patches:[{purl, uuid, action:"failed", errorCode, error}]}` (the `error` OBJECT is the vendored-mode error shape of the vendor-step fold-in above) and writes nothing — no `.socket/` on a fresh project; human mode prints `Error (): ` on stderr. **Already-vendored exemption**: a purl is exempt from the workspace refusal only when every instance of its `name@version` in `bun.lock` is already a `.socket/vendor/npm/…` local tuple (any uuid; the digest-less 2-tuple counts) — the engine's own criterion — so in-sync re-runs, `repair`, and a superseding patch uuid on a project vendored before it grew a workspace member all flow to the engine (re-pinning an already-local tuple adds no workspace-relative exposure); a wiped ledger alone is not a refusal (the engine path decides). UUID equality in the ledger alone never exempts a purl: `rollback --preserve-state` retains its record after unwiring. Dry-run refusal takes priority over `already_vendored`. **Unreadable vendor ledger**: a `.socket/vendor/state.json` the preflight cannot read or parse is itself the refusal — `vendor_state_unreadable` with the io/parse detail, fail-closed (nothing is exempt) — on the uuid path, the search / `scan` path and the `--dry-run` preview alike; never a Bun lock code. **`--silent`** is "errors only" and never mutes the refusal: the code-tagged `[error] (): ` (per-patch paths) / `Error (): …` (uuid path) line stays on stderr with an empty stdout. **`--dry-run`** previews the refusal as the additive `would_refuse` action (see `--dry-run` below). Agent-mode `get --save-only` is NOT preflighted (record-only intent has no consumption precondition). Pinned by `tests/in_process_vendor_bun.rs` (exact uuid-path envelope, seeded-manifest survival, `--silent`, `--dry-run`) and `tests/scan_vendor_e2e.rs`. +* **Vendored** (`get GHSA-… --mode vendored`): the download phase is scan's vendored posture — **manifest-free (v5.0)**: the selected records are fetched into memory (`download_patch_records`; blobs held in memory; nothing under `.socket/` is written; the nested apply never runs), then scan's vendor step runs under the apply lock over exactly the selected records, like `scan --mode vendored` (no whole-manifest scope and no `[note]` about other records — that blast radius is retired with the manifest; a legacy manifest record for a vendored purl is migrated out of `.socket/manifest.json` the same way scan does it). JSON: get's envelope takes the detached download envelope's shape — `{status, found, downloaded, skipped, failed, detached: true, patches: [{purl, uuid, action: "downloaded" | "skipped" | "failed", …}], warnings?}` (`applied` is absent; `detached: true` is pinned; a `downloaded` record for a purl the vendor ledger holds at another uuid carries the additive `oldUuid`, derived from the ledger — the human `[fetch]` line reads ` (replacing )`) — and gains the nested `vendor` Envelope exactly like scan's `result["vendor"]`; a vendor-step error folds the partial envelope + `{status:"error", error:{code,message}}` in (a pre-failure takeover reconcile may have already mutated the ledger — its events must reach the consumer). Exit: download failures or vendor `has_errors` → `partial_failure`/1. Human prompt: `Download and vendor N patches?`; `--dry-run` prints `[dry-run] Would download and vendor N patches. No changes made.` on both identifier paths (uuid and search). Telemetry mirrors scan's vendored arms (`track_outcomes_for_vendor` / `track_patch_vendor_failed`). **Bun vendored preflight (additive)** — shared by `get --mode vendored` on both its paths and `scan --mode vendored`: before ANY patch download, and only when the selection holds a `pkg:npm/` purl, the download phase reads `bun.lock`/`bun.lockb` once (`preflight_vendor`) and, when the vendor backend would refuse the project — a malformed, unreadable or unsupported `bun.lockb` → `vendor_bun_lockb_invalid`; an unreadable `bun.lock` → `vendor_lockfile_missing`; a `lockfileVersion` other than 0/1/2 or a non-canonical `packages` grammar → `vendor_lockfile_version_unsupported`; `workspace:` packages in a lock below version 2 → `vendor_bun_workspace_unsupported` — every `pkg:npm/` result becomes `{action:"failed", errorCode:, error:}` with NO fetch (the patch view is never requested) and no patch record; other ecosystems' results are untouched. **Search path** (`get --mode vendored`) and `scan --mode vendored`: the records ride `patches[]` / `download.patches[]` with `downloaded: 0`, the download phase writes nothing under `.socket/` (v5.0 — a pre-existing `.socket/manifest.json`, including a record seeded for another purl, is left byte-untouched), the vendor step still runs over the remaining records (no event for the refused purl), exit `partial_failure`/1. **uuid path** (`get --mode vendored`): the uuid lookup is the only fetch; the run exits 1 BEFORE the vendor step with exactly `{status:"error", found:1, downloaded:0, skipped:0, failed:1, error:{code, message}, patches:[{purl, uuid, action:"failed", errorCode, error}]}` (the `error` OBJECT is the vendored-mode error shape of the vendor-step fold-in above) and writes nothing — no `.socket/` on a fresh project; human mode prints `Error (): ` on stderr. **Already-vendored exemption**: a purl is exempt from the workspace refusal only when every instance of its `name@version` in `bun.lock` is already a `.socket/vendor/npm/…` local tuple (any uuid; the digest-less 2-tuple counts) — the engine's own criterion — so in-sync re-runs, `repair`, and a superseding patch uuid on a project vendored before it grew a workspace member all flow to the engine (re-pinning an already-local tuple adds no workspace-relative exposure); a wiped ledger alone is not a refusal (the engine path decides). UUID equality in the ledger alone never exempts a purl: `rollback --preserve-state` retains its record after unwiring. Dry-run refusal takes priority over `already_vendored`. **Unreadable vendor ledger**: a `.socket/vendor/state.json` the preflight cannot read or parse is itself the refusal — `vendor_state_unreadable` with the io/parse detail, fail-closed (nothing is exempt) — on the uuid path, the search / `scan` path and the `--dry-run` preview alike; never a Bun lock code. **`--silent`** is "errors only" and never mutes the refusal: the code-tagged `[error] (): ` (per-patch paths) / `Error (): …` (uuid path) line stays on stderr with an empty stdout. **`--dry-run`** previews the refusal as the additive `would_refuse` action (see `--dry-run` below). Agent-mode `get --save-only` is NOT preflighted (record-only intent has no consumption precondition). Pinned by `tests/vendor/in_process_vendor_bun.rs` (exact uuid-path envelope, seeded-manifest survival, `--silent`, `--dry-run`) and `tests/scan_vendor_e2e.rs`. -**Lock-text refusals before the download (v5.0)** — shared by `get --mode vendored` on both its paths and `scan --mode vendored`, after the Bun preflight above and the ledger's `already vendored` skip: a `pkg:npm/` result in a **pnpm, yarn classic or yarn berry** project, or a `pkg:cargo/` result, that its vendor backend refuses on the project's lock and manifest text alone is refused BEFORE its patch view is fetched — the pnpm / classic / berry gates the backend runs before it reads the package (coordinates, the lock and manifest reads and their line-ending / version / `cacheKey` / `.yarnrc.yml` gates, override and `resolutions` conflicts, the lock entry present and rewritable) and cargo's `locked_version_mismatch` (only when it is the crate's FIRST refusal; an in-tree `cargo vendor` copy still refuses in the loop as `already_vendored_in_tree`). **Scope:** only a package the vendor loop would hand to its backend is refused early — one installed on disk (the loop's own qualified-aware resolver plus the npm identity lookup), or one the lockfile inventory resolves to a verifiable registry source (a lock entry with an integrity, or the ledger-recovered pre-vendor resolution — exactly the entry the pristine fetch would use). A package absent from the lock and not installed never reached its backend and is untouched: its view is fetched, it downloads, and the vendor loop skips it `skipped` / `package_not_installed` as in v4.x (so cargo's `locked_version_mismatch` is refused early only for a crate installed at the unlocked version). The result becomes `{action:"failed", errorCode:, error:}` in `download.patches[]` / `patches[]` with the backend's exact code and detail, no view and no pristine fetch, no patch record, and therefore no vendor event: compared with v4.x, `download.downloaded` drops and `download.failed` rises by the number of such packages, `vendor.summary.failed` and `vendor.events` lose their `failed` events, and a lockfile-only package among them loses its `vendor_fetched_missing` event (it is never fetched). Exit code and top-level `status` are unchanged (`partial_failure`/1); the nested `vendor.status` becomes `success` when those refusals were the vendor step's only failures (observed on the depscan fixture: 3 refusals, `partialFailure` → `success`), and when every selected package is refused this way the human `scan --vendor` arm prints `Nothing was vendored: N patches failed (see above).`. **Precedence:** the lock-text refusal is decided before the view, so it wins over every view-derived outcome — a package that would also have been a paid-access 403 (`[PAID]`/no access), a failed view fetch, or a no-applicable-files skip reports the lock refusal instead (the Bun refusal and the ledger's `already vendored` skip still come first). The human `[error] (): ` line is printed during the download instead of the vendor step's failure line (the human (non-`--silent`) `scan --vendor` arm's baseline pre-check still fetches the views it verifies; only the download, the pristine fetch and the vendor step skip the package there). A purl the lockfiles pin hosted keeps the loop's refusal (its takeover restore rewrites the lock the gates read); other flavors (package-lock, pnpm-legacy, bun) and ecosystems are untouched, and `--dry-run` is unchanged. `vendor` (manifest-driven, no view fetch) keeps its per-package `failed` events but no longer fetches the pristine source of a lockfile-only package it refuses this way — the source is deferred to the backend, which refuses before reading it (no `vendor_fetched_missing` event and no registry request; a refused package whose registry is unreachable reports the gate's code instead of `vendor_fetch_failed`); only a package the lock resolves to a verifiable source is deferred, and one it does not resolve keeps its `package_not_installed` skip. Pinned by `tests/scan_vendor_e2e.rs` (`exact_download_plan`: scan and exact-purl get, pnpm and cargo scope), `tests/e2e_yarn_legacy_cachekey_refusal_build.rs` and `tests/vendor_rerun_no_network_e2e.rs`. +**Lock-text refusals before the download (v5.0)** — shared by `get --mode vendored` on both its paths and `scan --mode vendored`, after the Bun preflight above and the ledger's `already vendored` skip: a `pkg:npm/` result in a **pnpm, yarn classic or yarn berry** project, or a `pkg:cargo/` result, that its vendor backend refuses on the project's lock and manifest text alone is refused BEFORE its patch view is fetched — the pnpm / classic / berry gates the backend runs before it reads the package (coordinates, the lock and manifest reads and their line-ending / version / `cacheKey` / `.yarnrc.yml` gates, override and `resolutions` conflicts, the lock entry present and rewritable) and cargo's `locked_version_mismatch` (only when it is the crate's FIRST refusal; an in-tree `cargo vendor` copy still refuses in the loop as `already_vendored_in_tree`). **Scope:** only a package the vendor loop would hand to its backend is refused early — one installed on disk (the loop's own qualified-aware resolver plus the npm identity lookup), or one the lockfile inventory resolves to a verifiable registry source (a lock entry with an integrity, or the ledger-recovered pre-vendor resolution — exactly the entry the pristine fetch would use). A package absent from the lock and not installed never reached its backend and is untouched: its view is fetched, it downloads, and the vendor loop skips it `skipped` / `package_not_installed` as in v4.x (so cargo's `locked_version_mismatch` is refused early only for a crate installed at the unlocked version). The result becomes `{action:"failed", errorCode:, error:}` in `download.patches[]` / `patches[]` with the backend's exact code and detail, no view and no pristine fetch, no patch record, and therefore no vendor event: compared with v4.x, `download.downloaded` drops and `download.failed` rises by the number of such packages, `vendor.summary.failed` and `vendor.events` lose their `failed` events, and a lockfile-only package among them loses its `vendor_fetched_missing` event (it is never fetched). Exit code and top-level `status` are unchanged (`partial_failure`/1); the nested `vendor.status` becomes `success` when those refusals were the vendor step's only failures (observed on the depscan fixture: 3 refusals, `partialFailure` → `success`), and when every selected package is refused this way the human `scan --vendor` arm prints `Nothing was vendored: N patches failed (see above).`. **Precedence:** the lock-text refusal is decided before the view, so it wins over every view-derived outcome — a package that would also have been a paid-access 403 (`[PAID]`/no access), a failed view fetch, or a no-applicable-files skip reports the lock refusal instead (the Bun refusal and the ledger's `already vendored` skip still come first). The human `[error] (): ` line is printed during the download instead of the vendor step's failure line (the human (non-`--silent`) `scan --vendor` arm's baseline pre-check still fetches the views it verifies; only the download, the pristine fetch and the vendor step skip the package there). A purl the lockfiles pin hosted keeps the loop's refusal (its takeover restore rewrites the lock the gates read); other flavors (package-lock, pnpm-legacy, bun) and ecosystems are untouched, and `--dry-run` is unchanged. `vendor` (manifest-driven, no view fetch) keeps its per-package `failed` events but no longer fetches the pristine source of a lockfile-only package it refuses this way — the source is deferred to the backend, which refuses before reading it (no `vendor_fetched_missing` event and no registry request; a refused package whose registry is unreachable reports the gate's code instead of `vendor_fetch_failed`); only a package the lock resolves to a verifiable source is deferred, and one it does not resolve keeps its `package_not_installed` skip. Pinned by `tests/scan_vendor_e2e.rs` (`exact_download_plan`: scan and exact-purl get, pnpm and cargo scope), `tests/e2e_yarn_legacy_cachekey_refusal_build.rs` and `tests/vendor/vendor_rerun_no_network_e2e.rs`. * **Installed-version narrowing** (all modes, `get`'s search path): a CVE/GHSA fan-out returns one patch record per patched VERSION; get keeps only versions present here and emits calm `skipped` records (`errorCode: "package_not_installed"`) for the rest — never an error exit. Presence = installed on disk (qualified-aware resolver) ∪ already tracked in the manifest (record maintenance keeps working on hosts without an installed copy); hosted/vendored modes additionally count lockfile-resolved deps and vendor-ledger purls (mirroring scan's discovery supplements, including their `--global` gate). **Exempt** (no narrowing): UUID identifiers, exact-versioned PURL identifiers (explicit intent), `--save-only` runs (record-only has no installation precondition — the fresh-clone record→vendor flow keeps working), `--all-releases`, and the package-name path (already installed-derived). When EVERY found patch is filtered out, get exits 0 with the additive status **`not_installed`** (`{status:"not_installed", found:N, downloaded:0, applied:0, patches:[], warnings?}`) — never `no_match`, which remains pinned to the fuzzy package-name path. PnP layouts are surfaced, not misreported: yarn-PnP npm results skip with `errorCode: "yarn_pnp_unsupported"` in every mode; pnpm-PnP skips carry `pnpm_pnp_unsupported` in agent/vendored modes; hosted mode — the refusal's own remedy — keeps ONLY the versions the raw `pnpm-lock.yaml` text actually resolves (boundary-anchored probe over the v5/v6/v9 key spellings, so a large fan-out never requests grants for every version ever patched), labels a JUDGED miss `package_not_installed` exactly like a non-PnP project (the layout blocked nothing — the lock was read and the version isn't resolved), and reserves the layout code for an unreadable lock (no judgment possible). When EVERY narrowed-out result is a PnP refusal, the human terminal names the layout instead of claiming "not installed" and never advises `--all-releases` (which cannot make PnP patchable); the JSON status stays `not_installed` — consumers dispatch on the per-record `errorCode`. Hosted mode also runs the per-release VARIANT filter (`filter_to_installed_releases`) on its search path before requesting grants — agent/vendored runs get it inside the download engines — with the same keep-all-plus-warning fallbacks (surfaced as `(release_narrowing)`-prefixed strings in `warnings[]`). An ecosystem this binary has no crawler for is likewise never judged: its results are KEPT (absence from a crawl that never looked carries no information — the same fail-safe as scan's prune GC). The human `Found N patches:` listing shows only the patches whose package version survived the narrowing (the narrowing is judged over every result, so an installed package's paid fix a free user cannot download still lists as `[PAID] (no access)`, while skip records and counts cover only accessible patches), sorted by PURL in natural version order (`4.17.2` before `4.17.10`); the narrowed-out ones are summarized on stderr in one line per reason (`Skipped N patches for M package versions not installed here (use --all-releases to include them).`), and `--verbose` adds one `[skip] ()` line per skipped version after that summary, in natural version order. When the candidates hold more patches than were selected and the pick was made without a menu (a paid user's auto-pick, `--yes`, a non-TTY run), a `Selected:` block names the patch (purl, tier, short uuid, advisories) that will be installed before the prompt. Machine output (the prompt count, the JSON envelope) uses the kept set, unchanged. The finer per-release variant narrowing (`filter_to_installed_releases`) is unchanged and still runs inside the download engines (and before an agent-mode `--dry-run` preview, so the preview names only the variants a wet run would fetch). * **Deliberate divergences from scan** (documented, not drift): agent-mode get keeps its `selection_required` JSON posture for free multi-patch PURLs (scan and, v5.0, hosted/vendored get auto-pick); get has no `--vex` (an ambient `SOCKET_VEX` is ignored by get's modes), no `--prune`; get does not run scan's pre-vendor baseline annotation; and an all-narrowed-out run exits `not_installed` without entering the vendor step (heal-after-wipe re-vendoring stays `scan --mode vendored`'s job). Agent-mode `get` honors `--dry-run` too (v5.0): the search and uuid paths classify each selected patch against the manifest (read-only; an unreadable manifest fails closed like the wet run) and stop before the prompt, the download, any `.socket/` write and the apply — human `[would-add]` / `[would-update] … (replacing )` / `[skip] … (already in manifest)` lines then `[dry-run] Would download and apply N patches. No changes made.`; JSON `{status:"success", dryRun:true, found, downloaded:0, skipped, applied:0, patches:[{purl, uuid, action:"would_add"|"would_update"(+oldUuid)|"skipped"}, ], warnings?}`, exit 0. @@ -1464,7 +1464,7 @@ socket-patch apply --json | jq ' Exit `0` when `status` is `success`, `noManifest`, or `notFound`-with-zero-failed. Exit `1` when `status` is `partialFailure` (any `events[*].action == "failed"`) or `error`. -`apply` with no manifest at all is a clean exit-0 no-op (`status: "noManifest"`), and an **empty** manifest (zero patches) is a plain `success` exit 0 — this is load-bearing for CI steps that run `apply` after every install. A fully rolled-back agent project therefore keeps `.socket/manifest.json` at `{"patches": {}}`: the v5.0 residue rule never deletes a zero-patch manifest, precisely so these CI exits (and `list`'s 0-vs-1 below) never flip. Pinned by `tests/in_process_edge_cases.rs` and `tests/cli_dry_run_paths_e2e.rs`. **One carve-out**: a yarn-berry Plug'n'Play layout (`.pnp.*` loader at `--cwd`) refuses with the loud `yarn_pnp_unsupported` error (exit 1) even when no manifest exists — `scan` cannot discover PnP packages (they live inside `.yarn/cache/*.zip`, no `node_modules/`) and therefore never writes a manifest, so without the carve-out the documented refusal was unreachable and a PnP project's only signal was the calm noManifest exit. Pinned by `tests/e2e_safety_yarn_pnp.rs`. +`apply` with no manifest at all is a clean exit-0 no-op (`status: "noManifest"`), and an **empty** manifest (zero patches) is a plain `success` exit 0 — this is load-bearing for CI steps that run `apply` after every install. A fully rolled-back agent project therefore keeps `.socket/manifest.json` at `{"patches": {}}`: the v5.0 residue rule never deletes a zero-patch manifest, precisely so these CI exits (and `list`'s 0-vs-1 below) never flip. Pinned by `tests/in_process_edge_cases.rs` and `tests/cli/cli_dry_run_paths_e2e.rs`. **One carve-out**: a yarn-berry Plug'n'Play layout (`.pnp.*` loader at `--cwd`) refuses with the loud `yarn_pnp_unsupported` error (exit 1) even when no manifest exists — `scan` cannot discover PnP packages (they live inside `.yarn/cache/*.zip`, no `node_modules/`) and therefore never writes a manifest, so without the carve-out the documented refusal was unreachable and a PnP project's only signal was the calm noManifest exit. Pinned by `tests/e2e_safety_yarn_pnp.rs`. ## Exit codes diff --git a/crates/socket-patch-cli/src/commands/scan/mod.rs b/crates/socket-patch-cli/src/commands/scan/mod.rs index aa7b2ead9..38f8fdc6e 100644 --- a/crates/socket-patch-cli/src/commands/scan/mod.rs +++ b/crates/socket-patch-cli/src/commands/scan/mod.rs @@ -3243,7 +3243,7 @@ mod tests { // ---- redirectState envelope block (read-only cross-mode visibility) ---- // The end-to-end envelope placement (report-only + agent runs carry it, - // hosted/vendored runs don't) is pinned by `tests/scan_invariants.rs`; + // hosted/vendored runs don't) is pinned by `tests/scan/scan_invariants.rs`; // these pin the block builder's own gates and shape. /// Pins present ⇒ the block exists with each pin's canonical purl + diff --git a/crates/socket-patch-cli/tests/apply_invariants.rs b/crates/socket-patch-cli/tests/apply/apply_invariants.rs similarity index 99% rename from crates/socket-patch-cli/tests/apply_invariants.rs rename to crates/socket-patch-cli/tests/apply/apply_invariants.rs index ba9861ed1..2878ec307 100644 --- a/crates/socket-patch-cli/tests/apply_invariants.rs +++ b/crates/socket-patch-cli/tests/apply/apply_invariants.rs @@ -682,10 +682,8 @@ fn unmatched_purl_exit_semantics_are_pinned() { assert_eq!(v2["status"], "partialFailure", "{v2}"); } -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; +use crate::vlt_hosted_common; +use crate::vlt_vendored; /// A vlt-vendored purl: `apply` yields it to the vendor ledger (the /// committed dir artifact is what vlt installs), leaves `.socket/` diff --git a/crates/socket-patch-cli/tests/apply_network.rs b/crates/socket-patch-cli/tests/apply/apply_network.rs similarity index 100% rename from crates/socket-patch-cli/tests/apply_network.rs rename to crates/socket-patch-cli/tests/apply/apply_network.rs diff --git a/crates/socket-patch-cli/tests/cli_gem_variant_mismatch_policy.rs b/crates/socket-patch-cli/tests/apply/cli_gem_variant_mismatch_policy.rs similarity index 100% rename from crates/socket-patch-cli/tests/cli_gem_variant_mismatch_policy.rs rename to crates/socket-patch-cli/tests/apply/cli_gem_variant_mismatch_policy.rs diff --git a/crates/socket-patch-cli/tests/covgap_commands_apply.rs b/crates/socket-patch-cli/tests/apply/covgap_commands_apply.rs similarity index 99% rename from crates/socket-patch-cli/tests/covgap_commands_apply.rs rename to crates/socket-patch-cli/tests/apply/covgap_commands_apply.rs index 28d0caa33..035a48da9 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_apply.rs +++ b/crates/socket-patch-cli/tests/apply/covgap_commands_apply.rs @@ -39,8 +39,7 @@ use serde_json::{json, Value}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; +use crate::common; use common::{git_sha256, parse_json_envelope, run_with_env}; diff --git a/crates/socket-patch-cli/tests/e2e_safety_advisories.rs b/crates/socket-patch-cli/tests/apply/e2e_safety_advisories.rs similarity index 99% rename from crates/socket-patch-cli/tests/e2e_safety_advisories.rs rename to crates/socket-patch-cli/tests/apply/e2e_safety_advisories.rs index 359ffae47..284d07098 100644 --- a/crates/socket-patch-cli/tests/e2e_safety_advisories.rs +++ b/crates/socket-patch-cli/tests/apply/e2e_safety_advisories.rs @@ -27,8 +27,7 @@ use std::path::Path; -#[path = "common/mod.rs"] -mod common; +use crate::common; use common::{ git_sha256, parse_json_envelope, run_with_env, write_blob, write_minimal_manifest, PatchEntry, diff --git a/crates/socket-patch-cli/tests/in_process_gem_config_warning.rs b/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs similarity index 100% rename from crates/socket-patch-cli/tests/in_process_gem_config_warning.rs rename to crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs diff --git a/crates/socket-patch-cli/tests/in_process_gem_fallback_home.rs b/crates/socket-patch-cli/tests/apply/in_process_gem_fallback_home.rs similarity index 100% rename from crates/socket-patch-cli/tests/in_process_gem_fallback_home.rs rename to crates/socket-patch-cli/tests/apply/in_process_gem_fallback_home.rs diff --git a/crates/socket-patch-cli/tests/in_process_gem_multicopy.rs b/crates/socket-patch-cli/tests/apply/in_process_gem_multicopy.rs similarity index 100% rename from crates/socket-patch-cli/tests/in_process_gem_multicopy.rs rename to crates/socket-patch-cli/tests/apply/in_process_gem_multicopy.rs diff --git a/crates/socket-patch-cli/tests/in_process_npm_multicopy.rs b/crates/socket-patch-cli/tests/apply/in_process_npm_multicopy.rs similarity index 100% rename from crates/socket-patch-cli/tests/in_process_npm_multicopy.rs rename to crates/socket-patch-cli/tests/apply/in_process_npm_multicopy.rs diff --git a/crates/socket-patch-cli/tests/in_process_variant_apply_failure.rs b/crates/socket-patch-cli/tests/apply/in_process_variant_apply_failure.rs similarity index 100% rename from crates/socket-patch-cli/tests/in_process_variant_apply_failure.rs rename to crates/socket-patch-cli/tests/apply/in_process_variant_apply_failure.rs diff --git a/crates/socket-patch-cli/tests/apply/main.rs b/crates/socket-patch-cli/tests/apply/main.rs new file mode 100644 index 000000000..52d02b457 --- /dev/null +++ b/crates/socket-patch-cli/tests/apply/main.rs @@ -0,0 +1,21 @@ +//! `apply`: invariants, network behavior, silent/exit modes and the gem and npm multi-copy apply paths. +//! +//! One test binary per command: each module was its own binary. + +#[path = "../common/mod.rs"] +mod common; +#[path = "../vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; +#[path = "../vlt_hosted_common/vendored.rs"] +mod vlt_vendored; + +mod apply_invariants; +mod apply_network; +mod cli_gem_variant_mismatch_policy; +mod covgap_commands_apply; +mod e2e_safety_advisories; +mod in_process_gem_config_warning; +mod in_process_gem_fallback_home; +mod in_process_gem_multicopy; +mod in_process_npm_multicopy; +mod in_process_variant_apply_failure; diff --git a/crates/socket-patch-cli/tests/api_client_errors_e2e.rs b/crates/socket-patch-cli/tests/cli/api_client_errors_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/api_client_errors_e2e.rs rename to crates/socket-patch-cli/tests/cli/api_client_errors_e2e.rs diff --git a/crates/socket-patch-cli/tests/cli_dry_run_paths_e2e.rs b/crates/socket-patch-cli/tests/cli/cli_dry_run_paths_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/cli_dry_run_paths_e2e.rs rename to crates/socket-patch-cli/tests/cli/cli_dry_run_paths_e2e.rs diff --git a/crates/socket-patch-cli/tests/covgap_api_client.rs b/crates/socket-patch-cli/tests/cli/covgap_api_client.rs similarity index 100% rename from crates/socket-patch-cli/tests/covgap_api_client.rs rename to crates/socket-patch-cli/tests/cli/covgap_api_client.rs diff --git a/crates/socket-patch-cli/tests/covgap_commands_list.rs b/crates/socket-patch-cli/tests/cli/covgap_commands_list.rs similarity index 98% rename from crates/socket-patch-cli/tests/covgap_commands_list.rs rename to crates/socket-patch-cli/tests/cli/covgap_commands_list.rs index a41fceeaa..2e337c01e 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_list.rs +++ b/crates/socket-patch-cli/tests/cli/covgap_commands_list.rs @@ -20,12 +20,9 @@ use std::process::Command; use base64::Engine as _; use wiremock::MockServer; -#[path = "common/mod.rs"] -mod common; -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; +use crate::common; +use crate::vlt_hosted_common; +use crate::vlt_vendored; // --------------------------------------------------------------------------- // `--debug` provenance echoes (GlobalArgs::telemetry_credentials, config layer) diff --git a/crates/socket-patch-cli/tests/covgap_output.rs b/crates/socket-patch-cli/tests/cli/covgap_output.rs similarity index 99% rename from crates/socket-patch-cli/tests/covgap_output.rs rename to crates/socket-patch-cli/tests/cli/covgap_output.rs index b629bb8e3..25acecde0 100644 --- a/crates/socket-patch-cli/tests/covgap_output.rs +++ b/crates/socket-patch-cli/tests/cli/covgap_output.rs @@ -14,8 +14,6 @@ #![cfg(unix)] -#[path = "common/pty_io.rs"] -mod pty_io; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -26,8 +24,7 @@ use wiremock::{Mock, MockServer, ResponseTemplate}; // Pulled in for `git_sha256` (the patch-view blob fixture below must clear // the "patch has no applicable files" guardrail with a real blob // hash). Read-only reuse of the shared helper module. -#[path = "common/mod.rs"] -mod common; +use crate::common; const ORG_SLUG: &str = "test-org"; const UUID_A: &str = "11111111-1111-4111-8111-111111111111"; diff --git a/crates/socket-patch-cli/tests/interactive_prompts_e2e.rs b/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/interactive_prompts_e2e.rs rename to crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs index 25da2f439..6f744bfe4 100644 --- a/crates/socket-patch-cli/tests/interactive_prompts_e2e.rs +++ b/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs @@ -7,8 +7,6 @@ #![cfg(unix)] -#[path = "common/pty_io.rs"] -mod pty_io; use std::path::{Path, PathBuf}; use std::time::Duration; diff --git a/crates/socket-patch-cli/tests/cli/main.rs b/crates/socket-patch-cli/tests/cli/main.rs new file mode 100644 index 000000000..9ef09285e --- /dev/null +++ b/crates/socket-patch-cli/tests/cli/main.rs @@ -0,0 +1,21 @@ +//! Global CLI behavior: API client errors, dry-run paths, output modes, prompts, telemetry and `list`. +//! +//! One test binary per command: each module was its own binary. + +#[path = "../common/mod.rs"] +mod common; +#[path = "../common/pty_io.rs"] +mod pty_io; +#[path = "../vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; +#[path = "../vlt_hosted_common/vendored.rs"] +mod vlt_vendored; + +mod api_client_errors_e2e; +mod cli_dry_run_paths_e2e; +mod covgap_api_client; +mod covgap_commands_list; +mod covgap_output; +mod interactive_prompts_e2e; +mod output_modes_e2e; +mod telemetry_e2e; diff --git a/crates/socket-patch-cli/tests/output_modes_e2e.rs b/crates/socket-patch-cli/tests/cli/output_modes_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/output_modes_e2e.rs rename to crates/socket-patch-cli/tests/cli/output_modes_e2e.rs index aab9ef8bc..31291661d 100644 --- a/crates/socket-patch-cli/tests/output_modes_e2e.rs +++ b/crates/socket-patch-cli/tests/cli/output_modes_e2e.rs @@ -15,8 +15,7 @@ use std::path::Path; use sha2::{Digest, Sha256}; -#[path = "common/mod.rs"] -mod common; +use crate::common; fn git_sha256(content: &[u8]) -> String { let header = format!("blob {}\0", content.len()); diff --git a/crates/socket-patch-cli/tests/telemetry_e2e.rs b/crates/socket-patch-cli/tests/cli/telemetry_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/telemetry_e2e.rs rename to crates/socket-patch-cli/tests/cli/telemetry_e2e.rs diff --git a/crates/socket-patch-cli/tests/cli_get_silent_errors.rs b/crates/socket-patch-cli/tests/get/cli_get_silent_errors.rs similarity index 99% rename from crates/socket-patch-cli/tests/cli_get_silent_errors.rs rename to crates/socket-patch-cli/tests/get/cli_get_silent_errors.rs index 61dd38188..32b620431 100644 --- a/crates/socket-patch-cli/tests/cli_get_silent_errors.rs +++ b/crates/socket-patch-cli/tests/get/cli_get_silent_errors.rs @@ -12,8 +12,7 @@ use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; +use crate::common; const ORG: &str = "test-org"; const UUID: &str = "22222222-2222-4222-8222-222222222222"; diff --git a/crates/socket-patch-cli/tests/coverage_fix_get_double_json.rs b/crates/socket-patch-cli/tests/get/coverage_fix_get_double_json.rs similarity index 98% rename from crates/socket-patch-cli/tests/coverage_fix_get_double_json.rs rename to crates/socket-patch-cli/tests/get/coverage_fix_get_double_json.rs index 2c84264b0..430895942 100644 --- a/crates/socket-patch-cli/tests/coverage_fix_get_double_json.rs +++ b/crates/socket-patch-cli/tests/get/coverage_fix_get_double_json.rs @@ -12,8 +12,7 @@ use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; +use crate::common; const ORG: &str = "test-org"; const GHSA: &str = "GHSA-dbld-json-once"; diff --git a/crates/socket-patch-cli/tests/get_batch_paths_e2e.rs b/crates/socket-patch-cli/tests/get/get_batch_paths_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/get_batch_paths_e2e.rs rename to crates/socket-patch-cli/tests/get/get_batch_paths_e2e.rs diff --git a/crates/socket-patch-cli/tests/get_edge_cases_e2e.rs b/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/get_edge_cases_e2e.rs rename to crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs index 71e7433d2..e32b4ea97 100644 --- a/crates/socket-patch-cli/tests/get_edge_cases_e2e.rs +++ b/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs @@ -12,8 +12,7 @@ use wiremock::{Mock, MockServer, ResponseTemplate}; // inherits the developer's shell — `SOCKET_PROXY_URL` outranks the proxy // these tests pin, and `SOCKET_MANIFEST_PATH` makes a *passing* test write its manifest and // blobs into whatever real project the variable points at. -#[path = "common/mod.rs"] -mod common; +use crate::common; const ORG_SLUG: &str = "test-org"; const UUID_A: &str = "11111111-1111-4111-8111-111111111111"; diff --git a/crates/socket-patch-cli/tests/get_invariants.rs b/crates/socket-patch-cli/tests/get/get_invariants.rs similarity index 99% rename from crates/socket-patch-cli/tests/get_invariants.rs rename to crates/socket-patch-cli/tests/get/get_invariants.rs index 2e5b9b7ea..c79166973 100644 --- a/crates/socket-patch-cli/tests/get_invariants.rs +++ b/crates/socket-patch-cli/tests/get/get_invariants.rs @@ -8,8 +8,7 @@ use std::path::Path; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; +use crate::common; const ORG_SLUG: &str = "test-org"; const UUID: &str = "11111111-1111-4111-8111-111111111111"; diff --git a/crates/socket-patch-cli/tests/get_modes_e2e.rs b/crates/socket-patch-cli/tests/get/get_modes_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/get_modes_e2e.rs rename to crates/socket-patch-cli/tests/get/get_modes_e2e.rs index 0390330d5..8ba3bd979 100644 --- a/crates/socket-patch-cli/tests/get_modes_e2e.rs +++ b/crates/socket-patch-cli/tests/get/get_modes_e2e.rs @@ -21,12 +21,9 @@ use std::path::Path; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; -#[path = "npm_e2e_common/manifestless.rs"] -mod npm_e2e_common; -#[path = "vex_e2e_common/mod.rs"] -mod vex_e2e_common; +use crate::common; +use crate::npm_e2e_common; +use crate::vex_e2e_common; const ORG: &str = "test-org"; const NAME: &str = "getmodes-pkg"; @@ -235,7 +232,7 @@ async fn get_uuid_hosted_json_envelope_nests_redirect() { // committed the run writes no `.npmrc` (so `rewrittenFiles` stays the // lock alone) and emits exactly the already-set // `redirect_npm_allow_remote` caveat (the auto-config has its own suite: - // tests/redirect_npm_allow_remote.rs). + // tests/vendor/redirect_npm_allow_remote.rs). std::fs::write(tmp.path().join(".npmrc"), "allow-remote=all\n").unwrap(); let (code, stdout, stderr) = run_get( @@ -594,7 +591,7 @@ async fn get_hosted_dry_run_json_envelope() { // committed the run writes no `.npmrc` (so `rewrittenFiles` stays the // lock alone) and emits exactly the already-set // `redirect_npm_allow_remote` caveat (the auto-config has its own suite: - // tests/redirect_npm_allow_remote.rs). + // tests/vendor/redirect_npm_allow_remote.rs). std::fs::write(tmp.path().join(".npmrc"), "allow-remote=all\n").unwrap(); let lock_before = std::fs::read_to_string(tmp.path().join("package-lock.json")).unwrap(); diff --git a/crates/socket-patch-cli/tests/get_nested_apply_api_flags_e2e.rs b/crates/socket-patch-cli/tests/get/get_nested_apply_api_flags_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/get_nested_apply_api_flags_e2e.rs rename to crates/socket-patch-cli/tests/get/get_nested_apply_api_flags_e2e.rs index e55906b76..f16b21893 100644 --- a/crates/socket-patch-cli/tests/get_nested_apply_api_flags_e2e.rs +++ b/crates/socket-patch-cli/tests/get/get_nested_apply_api_flags_e2e.rs @@ -22,8 +22,7 @@ use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; +use crate::common; const ORG: &str = "test-org"; const UUID: &str = "11111111-1111-4111-8111-111111111111"; diff --git a/crates/socket-patch-cli/tests/get_update_summary_e2e.rs b/crates/socket-patch-cli/tests/get/get_update_summary_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/get_update_summary_e2e.rs rename to crates/socket-patch-cli/tests/get/get_update_summary_e2e.rs index 973839fd4..e68000d30 100644 --- a/crates/socket-patch-cli/tests/get_update_summary_e2e.rs +++ b/crates/socket-patch-cli/tests/get/get_update_summary_e2e.rs @@ -16,8 +16,7 @@ use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; +use crate::common; const ORG: &str = "test-org"; const OLD_UUID: &str = "00000000-0000-4000-8000-000000000000"; diff --git a/crates/socket-patch-cli/tests/global_packages_e2e.rs b/crates/socket-patch-cli/tests/get/global_packages_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/global_packages_e2e.rs rename to crates/socket-patch-cli/tests/get/global_packages_e2e.rs index 61d0f7d38..25bdadd21 100644 --- a/crates/socket-patch-cli/tests/global_packages_e2e.rs +++ b/crates/socket-patch-cli/tests/get/global_packages_e2e.rs @@ -24,8 +24,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; -#[path = "common/cache_env.rs"] -mod cache_env; +use crate::cache_env; fn binary() -> PathBuf { env!("CARGO_BIN_EXE_socket-patch").into() diff --git a/crates/socket-patch-cli/tests/get/main.rs b/crates/socket-patch-cli/tests/get/main.rs new file mode 100644 index 000000000..a06c0915a --- /dev/null +++ b/crates/socket-patch-cli/tests/get/main.rs @@ -0,0 +1,21 @@ +//! `get`: batch paths, edge cases, invariants, modes, nested apply flags, update summaries and global packages. +//! +//! One test binary per command: each module was its own binary. + +#[path = "../common/mod.rs"] +mod common; +#[path = "../npm_e2e_common/manifestless.rs"] +mod npm_e2e_common; +#[path = "../vex_e2e_common/mod.rs"] +mod vex_e2e_common; +use common::cache_env; + +mod cli_get_silent_errors; +mod coverage_fix_get_double_json; +mod get_batch_paths_e2e; +mod get_edge_cases_e2e; +mod get_invariants; +mod get_modes_e2e; +mod get_nested_apply_api_flags_e2e; +mod get_update_summary_e2e; +mod global_packages_e2e; diff --git a/crates/socket-patch-cli/tests/covgap_commands_remove.rs b/crates/socket-patch-cli/tests/remove/covgap_commands_remove.rs similarity index 99% rename from crates/socket-patch-cli/tests/covgap_commands_remove.rs rename to crates/socket-patch-cli/tests/remove/covgap_commands_remove.rs index de10372f9..1f5776505 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_remove.rs +++ b/crates/socket-patch-cli/tests/remove/covgap_commands_remove.rs @@ -13,16 +13,11 @@ //! remove_invariants.rs / remove_duality_invariants.rs / //! interactive_prompts_e2e.rs. -#[path = "common/pty_io.rs"] -mod pty_io; use std::path::{Path, PathBuf}; -#[path = "common/mod.rs"] -mod common; -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; +use crate::common; +use crate::vlt_hosted_common; +use crate::vlt_vendored; /// Spawn `socket-patch remove` with the scrubbed env plus telemetry /// disabled; `env` entries land last so per-test injections survive. diff --git a/crates/socket-patch-cli/tests/remove/main.rs b/crates/socket-patch-cli/tests/remove/main.rs new file mode 100644 index 000000000..ae74e97c5 --- /dev/null +++ b/crates/socket-patch-cli/tests/remove/main.rs @@ -0,0 +1,17 @@ +//! `remove`: invariants, duality with rollback, and network behavior. +//! +//! One test binary per command: each module was its own binary. + +#[path = "../common/mod.rs"] +mod common; +#[path = "../common/pty_io.rs"] +mod pty_io; +#[path = "../vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; +#[path = "../vlt_hosted_common/vendored.rs"] +mod vlt_vendored; + +mod covgap_commands_remove; +mod remove_duality_invariants; +mod remove_invariants; +mod remove_network; diff --git a/crates/socket-patch-cli/tests/remove_duality_invariants.rs b/crates/socket-patch-cli/tests/remove/remove_duality_invariants.rs similarity index 99% rename from crates/socket-patch-cli/tests/remove_duality_invariants.rs rename to crates/socket-patch-cli/tests/remove/remove_duality_invariants.rs index 5939d0dc4..8c147f1cd 100644 --- a/crates/socket-patch-cli/tests/remove_duality_invariants.rs +++ b/crates/socket-patch-cli/tests/remove/remove_duality_invariants.rs @@ -15,8 +15,7 @@ use std::path::{Path, PathBuf}; -#[path = "common/mod.rs"] -mod common; +use crate::common; /// Spawn `socket-patch remove` with the scrubbed env (`common::run_with_env`) /// plus telemetry disabled; `env` entries land last so per-test injections @@ -875,10 +874,8 @@ fn drift_kept_vendored_remove_is_partial_failure() { ); } -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; +use crate::vlt_hosted_common; +use crate::vlt_vendored; /// `remove --preserve-state` of a vlt-vendored purl restores the registry /// lock and package.json but keeps the directory artifact and the ledger diff --git a/crates/socket-patch-cli/tests/remove_invariants.rs b/crates/socket-patch-cli/tests/remove/remove_invariants.rs similarity index 99% rename from crates/socket-patch-cli/tests/remove_invariants.rs rename to crates/socket-patch-cli/tests/remove/remove_invariants.rs index 33ff85597..2a92ba270 100644 --- a/crates/socket-patch-cli/tests/remove_invariants.rs +++ b/crates/socket-patch-cli/tests/remove/remove_invariants.rs @@ -7,8 +7,7 @@ use std::path::{Path, PathBuf}; -#[path = "common/mod.rs"] -mod common; +use crate::common; const TWO_PATCH_MANIFEST: &str = r#"{ "patches": { @@ -1039,10 +1038,8 @@ fn remove_dry_run_with_rollback_does_not_create_blobs_dir() { assert!(litter.is_empty(), "no stage litter: {litter:?}"); } -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; +use crate::vlt_hosted_common; +use crate::vlt_vendored; /// `remove --skip-rollback` of a vlt-vendored purl drops the manifest /// record only: the vlt wiring, the directory artifact and the ledger entry diff --git a/crates/socket-patch-cli/tests/remove_network.rs b/crates/socket-patch-cli/tests/remove/remove_network.rs similarity index 100% rename from crates/socket-patch-cli/tests/remove_network.rs rename to crates/socket-patch-cli/tests/remove/remove_network.rs diff --git a/crates/socket-patch-cli/tests/coverage_fix_repair_vendor_predelete.rs b/crates/socket-patch-cli/tests/repair/coverage_fix_repair_vendor_predelete.rs similarity index 100% rename from crates/socket-patch-cli/tests/coverage_fix_repair_vendor_predelete.rs rename to crates/socket-patch-cli/tests/repair/coverage_fix_repair_vendor_predelete.rs diff --git a/crates/socket-patch-cli/tests/covgap_commands_repair.rs b/crates/socket-patch-cli/tests/repair/covgap_commands_repair.rs similarity index 100% rename from crates/socket-patch-cli/tests/covgap_commands_repair.rs rename to crates/socket-patch-cli/tests/repair/covgap_commands_repair.rs diff --git a/crates/socket-patch-cli/tests/covgap_commands_repair_vendor.rs b/crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs similarity index 99% rename from crates/socket-patch-cli/tests/covgap_commands_repair_vendor.rs rename to crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs index 647172917..bf4baf458 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_repair_vendor.rs +++ b/crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs @@ -1762,7 +1762,3 @@ async fn repair_inventory_refresh_persist_failure_stays_loud() { ); } -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; diff --git a/crates/socket-patch-cli/tests/repair/main.rs b/crates/socket-patch-cli/tests/repair/main.rs new file mode 100644 index 000000000..861a4d1dd --- /dev/null +++ b/crates/socket-patch-cli/tests/repair/main.rs @@ -0,0 +1,26 @@ +//! `repair`: invariants and vendored-tree repair across flavors. +//! +//! One test binary per command: each module was its own binary. + +#[path = "../common/mod.rs"] +mod common; +// `bun.rs` embeds its own copy of `vex_e2e_common`, which +// `repair_vendor_e2e` also loads directly. +#[allow(clippy::duplicate_mod)] +#[path = "../vex_e2e_common/bun.rs"] +mod bun_vex; +#[path = "../npm_e2e_common/manifestless.rs"] +mod npm_e2e_common; +#[path = "../vex_e2e_common/mod.rs"] +mod vex_e2e_common; +#[path = "../vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; +#[path = "../vlt_hosted_common/vendored.rs"] +mod vlt_vendored; + +mod coverage_fix_repair_vendor_predelete; +mod covgap_commands_repair; +mod covgap_commands_repair_vendor; +mod repair_invariants; +mod repair_vendor_e2e; +mod repair_vendor_flavors_e2e; diff --git a/crates/socket-patch-cli/tests/repair_invariants.rs b/crates/socket-patch-cli/tests/repair/repair_invariants.rs similarity index 100% rename from crates/socket-patch-cli/tests/repair_invariants.rs rename to crates/socket-patch-cli/tests/repair/repair_invariants.rs diff --git a/crates/socket-patch-cli/tests/repair_vendor_e2e.rs b/crates/socket-patch-cli/tests/repair/repair_vendor_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/repair_vendor_e2e.rs rename to crates/socket-patch-cli/tests/repair/repair_vendor_e2e.rs index ca3f90b3f..a12a741d9 100644 --- a/crates/socket-patch-cli/tests/repair_vendor_e2e.rs +++ b/crates/socket-patch-cli/tests/repair/repair_vendor_e2e.rs @@ -16,10 +16,8 @@ use sha2::{Digest, Sha256}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "npm_e2e_common/manifestless.rs"] -mod npm_e2e_common; -#[path = "vex_e2e_common/mod.rs"] -mod vex_e2e_common; +use crate::npm_e2e_common; +use crate::vex_e2e_common; fn binary() -> PathBuf { env!("CARGO_BIN_EXE_socket-patch").into() @@ -1263,10 +1261,8 @@ async fn repaired_vendored_state_attests_manifest_less() { } } -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; +use crate::vlt_hosted_common; +use crate::vlt_vendored; /// `repair --dry-run` over a deleted vlt directory artifact previews the /// rebuild (`wouldRebuild`, the dir path) and writes nothing; the wet run diff --git a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs b/crates/socket-patch-cli/tests/repair/repair_vendor_flavors_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs rename to crates/socket-patch-cli/tests/repair/repair_vendor_flavors_e2e.rs index f12d2aaff..44620f494 100644 --- a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs +++ b/crates/socket-patch-cli/tests/repair/repair_vendor_flavors_e2e.rs @@ -27,11 +27,9 @@ use sha2::{Digest, Sha256}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "vex_e2e_common/bun.rs"] -mod bun_vex; -#[path = "common/mod.rs"] -mod common; -#[path = "repair_vendor_flavors_e2e/vlt.rs"] +use crate::bun_vex; +use crate::common; +#[path = "../repair_vendor_flavors_e2e/vlt.rs"] mod vlt; const ORG_SLUG: &str = "test-org"; diff --git a/crates/socket-patch-cli/tests/cli_rollback_silent.rs b/crates/socket-patch-cli/tests/rollback/cli_rollback_silent.rs similarity index 100% rename from crates/socket-patch-cli/tests/cli_rollback_silent.rs rename to crates/socket-patch-cli/tests/rollback/cli_rollback_silent.rs diff --git a/crates/socket-patch-cli/tests/rollback/main.rs b/crates/socket-patch-cli/tests/rollback/main.rs new file mode 100644 index 000000000..46ae232be --- /dev/null +++ b/crates/socket-patch-cli/tests/rollback/main.rs @@ -0,0 +1,13 @@ +//! `rollback`: invariants, duality with remove, silent mode and the multi-copy blob gate. +//! +//! One test binary per command: each module was its own binary. + +#[path = "../vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; +#[path = "../vlt_hosted_common/vendored.rs"] +mod vlt_vendored; + +mod cli_rollback_silent; +mod rollback_duality_invariants; +mod rollback_invariants; +mod rollback_multicopy_blob_gate; diff --git a/crates/socket-patch-cli/tests/rollback_duality_invariants.rs b/crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs similarity index 100% rename from crates/socket-patch-cli/tests/rollback_duality_invariants.rs rename to crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs diff --git a/crates/socket-patch-cli/tests/rollback_invariants.rs b/crates/socket-patch-cli/tests/rollback/rollback_invariants.rs similarity index 99% rename from crates/socket-patch-cli/tests/rollback_invariants.rs rename to crates/socket-patch-cli/tests/rollback/rollback_invariants.rs index 4ae177caf..8a994e448 100644 --- a/crates/socket-patch-cli/tests/rollback_invariants.rs +++ b/crates/socket-patch-cli/tests/rollback/rollback_invariants.rs @@ -1064,10 +1064,8 @@ fn rollback_honors_manifest_path_override() { ); } -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; +use crate::vlt_hosted_common; +use crate::vlt_vendored; /// An unscoped `rollback` over a vlt-vendored project reverts the `file` /// node, its edges and package.json to the registry bytes and removes the diff --git a/crates/socket-patch-cli/tests/rollback_multicopy_blob_gate.rs b/crates/socket-patch-cli/tests/rollback/rollback_multicopy_blob_gate.rs similarity index 100% rename from crates/socket-patch-cli/tests/rollback_multicopy_blob_gate.rs rename to crates/socket-patch-cli/tests/rollback/rollback_multicopy_blob_gate.rs diff --git a/crates/socket-patch-cli/tests/coverage_fix_scan_discovery_corrupt_ledger.rs b/crates/socket-patch-cli/tests/scan/coverage_fix_scan_discovery_corrupt_ledger.rs similarity index 100% rename from crates/socket-patch-cli/tests/coverage_fix_scan_discovery_corrupt_ledger.rs rename to crates/socket-patch-cli/tests/scan/coverage_fix_scan_discovery_corrupt_ledger.rs diff --git a/crates/socket-patch-cli/tests/covgap_commands_fetch_stage.rs b/crates/socket-patch-cli/tests/scan/covgap_commands_fetch_stage.rs similarity index 100% rename from crates/socket-patch-cli/tests/covgap_commands_fetch_stage.rs rename to crates/socket-patch-cli/tests/scan/covgap_commands_fetch_stage.rs diff --git a/crates/socket-patch-cli/tests/covgap_commands_scan_vendor_flow.rs b/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs similarity index 99% rename from crates/socket-patch-cli/tests/covgap_commands_scan_vendor_flow.rs rename to crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs index cf23ca755..decd65db1 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_scan_vendor_flow.rs +++ b/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs @@ -660,10 +660,8 @@ async fn scan_vendor_staging_error_interactive_prints_error_line() { ); } -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; +use crate::vlt_hosted_common; +use crate::vlt_vendored; /// The dry-run preview over a vlt project already vendored at the offered /// uuid: the lock's `file` node is ours, so the vlt preflight exempts it diff --git a/crates/socket-patch-cli/tests/covgap_ecosystem_dispatch.rs b/crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs similarity index 100% rename from crates/socket-patch-cli/tests/covgap_ecosystem_dispatch.rs rename to crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs diff --git a/crates/socket-patch-cli/tests/hosted_management_refusals.rs b/crates/socket-patch-cli/tests/scan/hosted_management_refusals.rs similarity index 100% rename from crates/socket-patch-cli/tests/hosted_management_refusals.rs rename to crates/socket-patch-cli/tests/scan/hosted_management_refusals.rs diff --git a/crates/socket-patch-cli/tests/hosted_symlinked_files.rs b/crates/socket-patch-cli/tests/scan/hosted_symlinked_files.rs similarity index 99% rename from crates/socket-patch-cli/tests/hosted_symlinked_files.rs rename to crates/socket-patch-cli/tests/scan/hosted_symlinked_files.rs index 4ab06e221..431211e70 100644 --- a/crates/socket-patch-cli/tests/hosted_symlinked_files.rs +++ b/crates/socket-patch-cli/tests/scan/hosted_symlinked_files.rs @@ -28,10 +28,8 @@ use serde_json::{json, Value}; use wiremock::matchers::{method, path, path_regex}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; +use crate::common; +use crate::vlt_hosted_common; const ORG: &str = "test-org"; const CODE: &str = "redirect_symlinked_file_unsupported"; diff --git a/crates/socket-patch-cli/tests/hosted_wheel_metadata_order.rs b/crates/socket-patch-cli/tests/scan/hosted_wheel_metadata_order.rs similarity index 99% rename from crates/socket-patch-cli/tests/hosted_wheel_metadata_order.rs rename to crates/socket-patch-cli/tests/scan/hosted_wheel_metadata_order.rs index c6fe0ab6d..1d48d1e72 100644 --- a/crates/socket-patch-cli/tests/hosted_wheel_metadata_order.rs +++ b/crates/socket-patch-cli/tests/scan/hosted_wheel_metadata_order.rs @@ -23,8 +23,7 @@ use serde_json::{json, Value}; use wiremock::matchers::{method, path, path_regex}; use wiremock::{Mock, MockServer, Request, Respond, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; +use crate::common; const ORG: &str = "test-org"; diff --git a/crates/socket-patch-cli/tests/scan/main.rs b/crates/socket-patch-cli/tests/scan/main.rs new file mode 100644 index 000000000..5e0ce94f0 --- /dev/null +++ b/crates/socket-patch-cli/tests/scan/main.rs @@ -0,0 +1,25 @@ +//! `scan`: batching, ecosystem scope, ordering, paths, sync, hosted refusals and the vendor flow. +//! +//! One test binary per command: each module was its own binary. + +#[path = "../common/mod.rs"] +mod common; +#[path = "../vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; +#[path = "../vlt_hosted_common/vendored.rs"] +mod vlt_vendored; + +mod coverage_fix_scan_discovery_corrupt_ledger; +mod covgap_commands_fetch_stage; +mod covgap_commands_scan_vendor_flow; +mod covgap_ecosystem_dispatch; +mod hosted_management_refusals; +mod hosted_symlinked_files; +mod hosted_wheel_metadata_order; +mod scan_batch_sizing_e2e; +mod scan_ecosystems_scope_e2e; +mod scan_invariants; +mod scan_ordered_concurrency_e2e; +mod scan_paths_e2e; +mod scan_sync_e2e; +mod scan_vendor_step_error_e2e; diff --git a/crates/socket-patch-cli/tests/scan_batch_sizing_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_batch_sizing_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/scan_batch_sizing_e2e.rs rename to crates/socket-patch-cli/tests/scan/scan_batch_sizing_e2e.rs diff --git a/crates/socket-patch-cli/tests/scan_ecosystems_scope_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_ecosystems_scope_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/scan_ecosystems_scope_e2e.rs rename to crates/socket-patch-cli/tests/scan/scan_ecosystems_scope_e2e.rs diff --git a/crates/socket-patch-cli/tests/scan_invariants.rs b/crates/socket-patch-cli/tests/scan/scan_invariants.rs similarity index 99% rename from crates/socket-patch-cli/tests/scan_invariants.rs rename to crates/socket-patch-cli/tests/scan/scan_invariants.rs index 25616c2ab..abdf9d775 100644 --- a/crates/socket-patch-cli/tests/scan_invariants.rs +++ b/crates/socket-patch-cli/tests/scan/scan_invariants.rs @@ -2112,10 +2112,8 @@ async fn ecosystems_filter_keeps_records_but_not_wiring_live() { ); } -#[path = "vlt_hosted_common/mod.rs"] -mod vlt_hosted_common; -#[path = "vlt_hosted_common/vendored.rs"] -mod vlt_vendored; +use crate::vlt_hosted_common; +use crate::vlt_vendored; /// vlt twin of `scan_agent_over_vendored_purl_surfaces_run_level_warning`: /// an agent scan over a purl a vlt directory artifact vendors skips it diff --git a/crates/socket-patch-cli/tests/scan_ordered_concurrency_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_ordered_concurrency_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/scan_ordered_concurrency_e2e.rs rename to crates/socket-patch-cli/tests/scan/scan_ordered_concurrency_e2e.rs diff --git a/crates/socket-patch-cli/tests/scan_paths_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/scan_paths_e2e.rs rename to crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs diff --git a/crates/socket-patch-cli/tests/scan_sync_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_sync_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/scan_sync_e2e.rs rename to crates/socket-patch-cli/tests/scan/scan_sync_e2e.rs diff --git a/crates/socket-patch-cli/tests/scan_vendor_step_error_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_vendor_step_error_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/scan_vendor_step_error_e2e.rs rename to crates/socket-patch-cli/tests/scan/scan_vendor_step_error_e2e.rs diff --git a/crates/socket-patch-cli/tests/covgap_commands_update.rs b/crates/socket-patch-cli/tests/update/covgap_commands_update.rs similarity index 99% rename from crates/socket-patch-cli/tests/covgap_commands_update.rs rename to crates/socket-patch-cli/tests/update/covgap_commands_update.rs index 809bb3c72..b2d75aafc 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_update.rs +++ b/crates/socket-patch-cli/tests/update/covgap_commands_update.rs @@ -9,12 +9,8 @@ //! itself must never be a swap target. Fixture shapes are copied from //! self_update_e2e.rs / interactive_prompts_e2e.rs. -#[path = "common/pty_io.rs"] -mod pty_io; -#[path = "common/mod.rs"] -mod common; -#[path = "common/update_fixture.rs"] -mod update_fixture; +use crate::common; +use crate::update_fixture; use update_fixture::{make_served_binary, run_installed, staged_install, FakeReleaseBuilder}; diff --git a/crates/socket-patch-cli/tests/covgap_update_download.rs b/crates/socket-patch-cli/tests/update/covgap_update_download.rs similarity index 96% rename from crates/socket-patch-cli/tests/covgap_update_download.rs rename to crates/socket-patch-cli/tests/update/covgap_update_download.rs index 6bf578edf..18cf2e018 100644 --- a/crates/socket-patch-cli/tests/covgap_update_download.rs +++ b/crates/socket-patch-cli/tests/update/covgap_update_download.rs @@ -9,10 +9,8 @@ //! a failed update must leave the install byte-identical with no stage //! droppings. -#[path = "common/mod.rs"] -mod common; -#[path = "common/update_fixture.rs"] -mod update_fixture; +use crate::common; +use crate::update_fixture; use wiremock::matchers::{method, path as urlpath}; use wiremock::{Mock, ResponseTemplate}; diff --git a/crates/socket-patch-cli/tests/covgap_update_swap.rs b/crates/socket-patch-cli/tests/update/covgap_update_swap.rs similarity index 98% rename from crates/socket-patch-cli/tests/covgap_update_swap.rs rename to crates/socket-patch-cli/tests/update/covgap_update_swap.rs index 2730744a1..bb9c4f8b6 100644 --- a/crates/socket-patch-cli/tests/covgap_update_swap.rs +++ b/crates/socket-patch-cli/tests/update/covgap_update_swap.rs @@ -16,10 +16,7 @@ //! `CARGO_BIN_EXE_socket-patch` itself must never be a swap target — and //! re-verifies the real artifact at the end. -#[path = "common/mod.rs"] -mod common; -#[path = "common/update_fixture.rs"] -mod update_fixture; +use crate::update_fixture; use std::path::Path; diff --git a/crates/socket-patch-cli/tests/update/main.rs b/crates/socket-patch-cli/tests/update/main.rs new file mode 100644 index 000000000..0b4629b5b --- /dev/null +++ b/crates/socket-patch-cli/tests/update/main.rs @@ -0,0 +1,16 @@ +//! Self-update channels, downloads, swaps and the update notifier. +//! +//! One test binary per command: each module was its own binary. + +#[path = "../common/mod.rs"] +mod common; +#[path = "../common/pty_io.rs"] +mod pty_io; +#[path = "../common/update_fixture.rs"] +mod update_fixture; + +mod covgap_commands_update; +mod covgap_update_download; +mod covgap_update_swap; +mod self_update_channels_e2e; +mod update_notifier_e2e; diff --git a/crates/socket-patch-cli/tests/self_update_channels_e2e.rs b/crates/socket-patch-cli/tests/update/self_update_channels_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/self_update_channels_e2e.rs rename to crates/socket-patch-cli/tests/update/self_update_channels_e2e.rs index a13fb56f6..526100ba9 100644 --- a/crates/socket-patch-cli/tests/self_update_channels_e2e.rs +++ b/crates/socket-patch-cli/tests/update/self_update_channels_e2e.rs @@ -16,10 +16,8 @@ //! the prefix comparison never fires — which is exactly the behavior the //! real cargo/launcher installers see, since they resolve real paths. -#[path = "common/mod.rs"] -mod common; -#[path = "common/update_fixture.rs"] -mod update_fixture; +use crate::common; +use crate::update_fixture; use sha2::{Digest, Sha256}; use update_fixture::{ diff --git a/crates/socket-patch-cli/tests/update_notifier_e2e.rs b/crates/socket-patch-cli/tests/update/update_notifier_e2e.rs similarity index 99% rename from crates/socket-patch-cli/tests/update_notifier_e2e.rs rename to crates/socket-patch-cli/tests/update/update_notifier_e2e.rs index 6589c99f9..4e679d76a 100644 --- a/crates/socket-patch-cli/tests/update_notifier_e2e.rs +++ b/crates/socket-patch-cli/tests/update/update_notifier_e2e.rs @@ -11,10 +11,8 @@ //! without a manifest, and `--version`/`--help` never dispatch (clap //! handles them before the hook), so none of those can carry the notifier. -#[path = "common/mod.rs"] -mod common; -#[path = "common/update_fixture.rs"] -mod update_fixture; +use crate::common; +use crate::update_fixture; use std::path::Path; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; diff --git a/crates/socket-patch-cli/tests/docker_vendor_common_selftest.rs b/crates/socket-patch-cli/tests/vendor/docker_vendor_common_selftest.rs similarity index 98% rename from crates/socket-patch-cli/tests/docker_vendor_common_selftest.rs rename to crates/socket-patch-cli/tests/vendor/docker_vendor_common_selftest.rs index 729f77838..4677da7ea 100644 --- a/crates/socket-patch-cli/tests/docker_vendor_common_selftest.rs +++ b/crates/socket-patch-cli/tests/vendor/docker_vendor_common_selftest.rs @@ -15,8 +15,7 @@ use std::process::{Command, Output, Stdio}; use sha2::{Digest, Sha256}; -#[path = "docker_vendor_common/mod.rs"] -mod docker_vendor_common; +use crate::docker_vendor_common; use docker_vendor_common::{bash_prelude, stage_patch_fn}; diff --git a/crates/socket-patch-cli/tests/e2e_golang_redirect.rs b/crates/socket-patch-cli/tests/vendor/e2e_golang_redirect.rs similarity index 99% rename from crates/socket-patch-cli/tests/e2e_golang_redirect.rs rename to crates/socket-patch-cli/tests/vendor/e2e_golang_redirect.rs index 666220ce1..3bcfee7d5 100644 --- a/crates/socket-patch-cli/tests/e2e_golang_redirect.rs +++ b/crates/socket-patch-cli/tests/vendor/e2e_golang_redirect.rs @@ -12,8 +12,7 @@ use std::os::unix::fs::PermissionsExt; use std::path::Path; -#[path = "common/mod.rs"] -mod common; +use crate::common; use common::{binary, git_sha256, git_sha256_file, write_blob, write_minimal_manifest, PatchEntry}; diff --git a/crates/socket-patch-cli/tests/in_process_vendor_bun.rs b/crates/socket-patch-cli/tests/vendor/in_process_vendor_bun.rs similarity index 99% rename from crates/socket-patch-cli/tests/in_process_vendor_bun.rs rename to crates/socket-patch-cli/tests/vendor/in_process_vendor_bun.rs index 38f15e787..937a6e484 100644 --- a/crates/socket-patch-cli/tests/in_process_vendor_bun.rs +++ b/crates/socket-patch-cli/tests/vendor/in_process_vendor_bun.rs @@ -38,10 +38,8 @@ use std::time::{Duration, Instant}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "vex_e2e_common/bun.rs"] -mod bun_vex; -#[path = "common/mod.rs"] -mod common; +use crate::bun_vex; +use crate::common; const ORG: &str = "test-org"; const UUID: &str = "11111111-1111-4111-8111-111111111111"; diff --git a/crates/socket-patch-cli/tests/vendor/main.rs b/crates/socket-patch-cli/tests/vendor/main.rs new file mode 100644 index 000000000..41bd1f72b --- /dev/null +++ b/crates/socket-patch-cli/tests/vendor/main.rs @@ -0,0 +1,18 @@ +//! `vendor`: lockfile-only gems, pristine fetch order, no-network re-runs, bun and the hosted redirects. +//! +//! One test binary per command: each module was its own binary. + +#[path = "../vex_e2e_common/bun.rs"] +mod bun_vex; +#[path = "../common/mod.rs"] +mod common; +#[path = "../docker_vendor_common/mod.rs"] +mod docker_vendor_common; + +mod docker_vendor_common_selftest; +mod e2e_golang_redirect; +mod in_process_vendor_bun; +mod redirect_npm_allow_remote; +mod vendor_gem_lockfile_only_e2e; +mod vendor_pristine_fetch_order_e2e; +mod vendor_rerun_no_network_e2e; diff --git a/crates/socket-patch-cli/tests/redirect_npm_allow_remote.rs b/crates/socket-patch-cli/tests/vendor/redirect_npm_allow_remote.rs similarity index 99% rename from crates/socket-patch-cli/tests/redirect_npm_allow_remote.rs rename to crates/socket-patch-cli/tests/vendor/redirect_npm_allow_remote.rs index 1c38ab3b2..6314f1687 100644 --- a/crates/socket-patch-cli/tests/redirect_npm_allow_remote.rs +++ b/crates/socket-patch-cli/tests/vendor/redirect_npm_allow_remote.rs @@ -33,8 +33,7 @@ use serde_json::{json, Value}; use wiremock::matchers::{method, path, path_regex}; use wiremock::{Mock, MockServer, ResponseTemplate}; -#[path = "common/mod.rs"] -mod common; +use crate::common; const ORG: &str = "test-org"; const NAME: &str = "allow-remote-dep"; diff --git a/crates/socket-patch-cli/tests/vendor_gem_lockfile_only_e2e.rs b/crates/socket-patch-cli/tests/vendor/vendor_gem_lockfile_only_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/vendor_gem_lockfile_only_e2e.rs rename to crates/socket-patch-cli/tests/vendor/vendor_gem_lockfile_only_e2e.rs diff --git a/crates/socket-patch-cli/tests/vendor_pristine_fetch_order_e2e.rs b/crates/socket-patch-cli/tests/vendor/vendor_pristine_fetch_order_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/vendor_pristine_fetch_order_e2e.rs rename to crates/socket-patch-cli/tests/vendor/vendor_pristine_fetch_order_e2e.rs diff --git a/crates/socket-patch-cli/tests/vendor_rerun_no_network_e2e.rs b/crates/socket-patch-cli/tests/vendor/vendor_rerun_no_network_e2e.rs similarity index 100% rename from crates/socket-patch-cli/tests/vendor_rerun_no_network_e2e.rs rename to crates/socket-patch-cli/tests/vendor/vendor_rerun_no_network_e2e.rs diff --git a/crates/socket-patch-core/src/crawlers/maven_pom_equivalence_tests.rs b/crates/socket-patch-core/src/crawlers/maven_pom_equivalence_tests.rs index 3eb33b61a..1f2de7d8a 100644 --- a/crates/socket-patch-core/src/crawlers/maven_pom_equivalence_tests.rs +++ b/crates/socket-patch-core/src/crawlers/maven_pom_equivalence_tests.rs @@ -1,273 +1,11 @@ -//! Equivalence oracle for the POM coordinate parser, which now uses static -//! tag needles, borrows comment-free lines, skips tag-free lines and stops -//! once the project's own coordinates are complete. The previous -//! implementation is kept here verbatim; the production parser must return -//! the identical result on every document. +//! Seeded POM documents for the coordinate parser, which uses static tag +//! needles, borrows comment-free lines, skips tag-free lines and stops once +//! the project's own coordinates are complete. Each sweep is pinned by a +//! golden (see `crate::golden`) blessed when #257's verbatim previous +//! parser and this one agreed on every document. use super::maven_crawler::parse_pom_group_artifact_version; - -/// Extract the text value between `` and `` on a single line. -fn extract_xml_value(line: &str, element: &str) -> Option { - let open = format!("<{element}>"); - let close = format!(""); - let start = line.find(&open)?; - let value_start = start + open.len(); - let end = line[value_start..].find(&close)?; - let value = line[value_start..value_start + end].trim().to_string(); - if value.is_empty() { - None - } else { - Some(value) - } -} - -/// Strip the commented-out portions of a single line, threading the -/// `in_comment` state across lines so multi-line `` blocks are -/// handled. XML comments do not nest, so we always close on the first `-->`. -/// -/// This runs before any tag matching: POM files routinely carry license -/// headers and commented-out ``/`` snippets, and naive -/// substring matching would otherwise miscount skip-section depth (e.g. a -/// comment containing `` could "close" a block that is still open -/// and leak a plugin's coordinates as the project's). -fn strip_comment_spans(line: &str, in_comment: &mut bool) -> String { - let mut out = String::new(); - let mut rest = line; - loop { - if *in_comment { - match rest.find("-->") { - Some(end) => { - rest = &rest[end + 3..]; - *in_comment = false; - } - None => return out, // remainder of the line is inside a comment - } - } else { - match rest.find("