test(e2e): npm, pnpm and yarn clients for the harness, and the 27 catalog rows they cover
What this adds
An npm client for the e2e harness, a pnpm and a yarn client beside it, and the
27 npm catalog rows they make assertable. The catalog had 56 automatable non-UI rows and no way to write any
of them: e2e/internal/client held oras and oci only, and the pattern
registry named no npm scenario.
| Group | Rows covered | Left in the group |
|---|---|---|
| Setup and configuration | 2 of 2 | — |
| Authentication | 3 of 3 | ci-job-token (blocked on job-scoped credentials) |
| Publish | 8 of 8 | — |
| Discover | 2 of 2 | — |
| Consume | 6 of 6 | — |
| Lifecycle | 6 of 8 | 2 counter rows, both blocked (below) |
e2e-catalog check reports 0 disagreements; covered goes 5 → 32 across all
catalogs, and the npm catalog's uncovered count falls by 27.
The client
e2e/internal/client/npm writes its own confinement rather than reusing
client/oci's. npm resolves its user config and cache from
npm_config_userconfig and npm_config_cache, reads nothing from
DOCKER_CONFIG, and keeps its credential in a file the client writes rather
than a header it passes. Its runner splits stdout from stderr, because this
output is parsed and npm warns on stderr.
Versions asserts node against the .tool-versions pin and only reports
npm's own: npm ships inside Node and nothing in this repository declares a
version for it, so there is no pin to compare against. nodejs joins
install-tools.sh's MISE_TOOLS, which had omitted it, and pnpm and yarn
join it with pins of their own.
The two sibling clients, measured rather than assumed
Both were driven against a stub registry before a line of the tests was written.
pnpm reads the same .npmrc through npm_config_userconfig and takes no
different flags for publish, so client/pnpm holds its pin and hands back the
npm client set to invoke it.
yarn 4 needs its own package. It reads .yarnrc.yml and ignores .npmrc,
resolves its cache and global folder from YARN_* variables, spells the publish
yarn npm publish, and needs three commands rather than one: a directory with no
lockfile is read as part of an enclosing project, and yarn npm publish refuses
a workspace the lockfile does not resolve. It also rejects a plaintext registry
outright (YN0081), so Configure writes unsafeHttpWhitelist with the
registry host — only when the scheme is http, so an https rig carries no
opt-in it never required. That carve-out has its own entry in the
upstream-limitations index.
Both rows publish with the sibling and install with npm, comparing against the payload npm's own publish writes. Installing with the same tool would not show that the two agree, which is what "the same installable result as the npm CLI" claims.
Two rows this stops short of, both verified
lifecycle.unpublish-package-counters is blocked, not skipped. Both
unpublish shapes it describes end with the npm_packages row soft-deleted — a
whole-package unpublish marks it, and unpublishing a package's last active
version cascades to it via cascadeEmptyPackageTx, which ADR 007 states and
#988
records as missing from S11's text. ListNpmPackages and
FindNpmPackageInRepository both filter soft_deleted_at IS NULL, so the
tags_count reset the unpublish writes lands on a row no management-API read
serves. The section intro names the missing capability.
discover.metadata-after-rebuild is now manual, with both reasons in the
row. A rebuild leaves no signal a client can read — streamDocument frames the
same ETag, Cache-Control, Content-Type and Content-Length whether it
serves a pre-built cache row or builds inline — and its multi-page half needs
1001 versions in one package, since packumentReadPageSize is a Go constant no
rig setting lowers.
lifecycle.management-delete-storage-counters stays uncovered and the section
intro says why: two of its clauses are about a remote repository's deletes, and
e2e/internal/registry cannot yet create a repository of kind: remote. It
travels with the ## Remote repositories group, which needs the same capability.
Catalog corrections
The remote section claimed a remote repository "cannot be created through the
management API yet, so the test environment seeds the npm_remote_repositories
binding out of band". It can: api/openapi/v1.yaml documents a create taking
kind: remote with a settings.url, and internal/datastore/repositories.go
dispatches it to CreateNpmRemoteRepository.
Four remote cells paired an HTTP-observable outcome with a claim about rows and
blobs nothing outside the database can see. Those clauses moved into the section
intro as explanation, pointing at S15, so each cell states only what a client or
the management API observes. ## Scope now records that the suite reads no
database row and no object storage, and names the two delivery-mode-override
rows that leaves uncovered.
Rig configuration, and what it hides
Three settings land in both test/caproni/values/ files, following the
iam.cache_ttl: "0s" precedent already there:
npm.public_registry_url— unset, the registry renders every packument'sdist.tarballroot-relative, which npm resolves but no deployment serves.npm.max_tarball_size: "512KB"andnpm.max_versions_per_package: 5— lowered from ADR-004's 5 GB and 25000, neither reachable in CI.
ADR-004 governs the shipped defaults, which are unchanged; these are rig
settings. e2e/README.md's upstream-limitations index records what the lowered
caps hide: the suite proves each cap is enforced, never that it is enforced at
the value production ships. The index also gains npm's client-side
duplicate-publish refusal, which is why republish-same-version covers both a
CLI arm and a raw PUT.
Reviewable size
Past the 500 LOC guideline, and not split. By file group:
| Group | LOC | Why it travels here |
|---|---|---|
e2e/internal/client/{npm,pnpm,yarn} (7 files) |
~1200 | Each capability plus its unit tests; the no-untested-code guardrail keeps them together |
e2e/bootstrap/npm (7 files) |
~1150 | One file per catalog group, which is how a reviewer reads one coherent set |
patterns.go, harness wiring |
~250 | One registration per row, each naming its exemplar |
| Catalog, README, rig values, tool pins | ~520 | The Status flips and corrections the rows above require |
Splitting the client from its scenarios would land a client no row uses; splitting the groups would land pattern registrations without their tests. The 13 commits are individually reviewable and each states what it asserts and why.
Not verified against a rig
Nothing here has run against a live caproni cluster. Every test(...) commit
says so. The riskiest constructions are PublishEnvelope's key order and the
coherence-mismatch construction, modeled on the envelope builder in
internal/format/npm's publish pre-check integration tests. Its required
key set is settled, though: streampub/walker.go:134 and :138 name name,
versions and _attachments as the only three, so an absent dist-tags cannot
divert a raw PUT to a missing-field error. The pnpm and yarn client behaviours
were measured locally against a stub registry, including that yarn handles a
path-bearing registry URL.
Merge order
Third in a stack. !2052 (main ← sahmed/e2e-caproni-authz) merges first,
then !2070 (merged) (sahmed/e2e-caproni-authz ← sahmed/e2e-oci-setup-scenarios),
then this one. It targets sahmed/e2e-oci-setup-scenarios because it depends on
that branch's registry.Client.DeleteRepository and its oci setup exemplar,
which the two npm setup rows and their pattern registrations both cite.
This is test and documentation work with no behavior change, so no issue link is required per the conventions' maintenance exemption.