test(npm): dist-tags integration suite (S11 Step 10, MR 5/5)
📦 What
S11 Step 10 (dist-tags GET endpoint) shipped as a five-MR stack, in dependency order:
- !622 (merged) - dist-tags handler primitives + in-package unit tests.
- !623 (merged) - inline-build handler (resolve -> 404 -> build
{tag:version}-> serve). - !624 (merged) - kind=1
npm_metadata_filescache fast-path + conditional-GET 304-without-blob-read. - !625 (merged) - inline-build singleflight (collapse concurrent cache misses).
- !626 (merged) - integration suite (real datastore finders + stub
BlobStore). <- this MR
All four parents (!622 (merged)-!625 (merged)) have merged; this MR now targets main. As the final slice it also closes out Step 10 in the npm-hosted plan (the Step -> MR mapping table).
Slice 5 - the integration suite driving npm.DistTagsHandler against the real datastore read finders (NpmPackageStore, NpmMetadataFileStore, NpmTagStore, NpmVersionStore, RepositoryStore) over testcontainers PostgreSQL, plus an in-memory stub BlobStore seeded with the cache-blob bytes. The handler is constructed directly with the two-id Resolution injected into context (the route is on the 501 placeholder; see below).
- AC 34 - a missing
npm_packagesrow and a soft-deleted one both return 404package_not_found. - Cache-served - a fresh kind=1
npm_metadata_filesrow serves the blob with the strong ETag and publicCache-Control. - Inline-build - a cache miss with tags renders
{tag:version}; a version with no tag row renders{}(no synthesizedlatest); a private repo servesprivate, max-age=0; an expired kind=1 row falls through to the inline build (the stale blob is bypassed, not served). - A content-addressed fixture distinctness guard pins that distinct dist-tags bodies seed distinct cache digests.
✅ Spec coverage
| Behaviour | Test |
|---|---|
AC 34 - missing and soft-deleted package -> 404 package_not_found |
TestDistTagsIntegration_PackageNotFound |
Cache-served 200: strong ETag + public Cache-Control (real metadata-file finder) |
TestDistTagsIntegration_CacheServed |
Inline-build {tag:version} and {} (no synthesized latest), private Cache-Control, expired-row fall-through |
TestDistTagsIntegration_InlineBuild |
| Content-addressed cache fixtures stay distinct | TestDistTagsIntegration_DistinctCacheDigests |
| AC 58 - read-endpoint auth (401/403/404-on-private) | S08-owned: auth middleware runs before npm code; the handler has no auth surface. Not exercised here. |
⚠️ Known gap - handler not yet wired into the route table
DistTagsHandler is implemented and tested, but nothing mounts it: internal/format/npm/handler.go still serves the 501 Not Implemented placeholder for the dist-tags route, so the integration tests construct the handler directly with an injected Resolution rather than routing through the mux. This is an unassigned plan-level gap (the npm hosted plan creates handler.go with placeholders in Step 8 and never assigns the placeholder->real-handler swap to a handler step); it surfaces no later than Step 23 (real npm-CLI conformance) and is closed via a plan amendment, not bolted onto this stack. Deliberately out of scope.
🧪 Testing
go test ./internal/format/npm/...- passgolangci-lint run ./internal/format/npm/...- 0 issuesgo vet -tags integration ./internal/format/npm/...- clean- Integration tests (
//go:build integration) run in the merged-results pipeline.
Related to #122 (closed)