Every tagged manifest PUT left two rows in `tag`: one expired a few milliseconds after creation and one live. Distribution's manifest handler stores the manifest with its tag (PutManifest -> putTag) and then calls the tag service for the same tag (PutTag -> putTag), and the second write expired the row the first had just created. The manifest middleware now records, in the request-scoped holder, the tag and digest it stored. The tag service skips the store write when it is asked for that same tag and digest within the same request. putTag itself is unchanged: a later push of a tag, identical or retargeted, expires the live row and adds a new one, the way Quay's retarget_tag records tag history. Result is one row per push instead of two. Claude-Session: https://claude.ai/code/session_01K3wbqTw3owBi8RT352YDoP Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Go end-to-end tests
This tree contains in-process, black-box tests for complete Go application compositions. These tests sit above package integration tests and below process-level installation and conformance jobs: they start a real application handler on a loopback listener and exercise it through its public HTTP boundary.
Layout
Keep tests grouped by product:
internal/e2e/
└── mirrorregistry/
├── registry_test.go
└── internal/e2etest/
├── harness.go
└── registry.go
A product directory owns its fixtures, clients, and scenarios. New mirror-registry scenarios belong under mirrorregistry/; a different composed product should get a sibling directory rather than sharing one large test suite.
The second internal/ is intentional. Go restricts mirrorregistry/internal/e2etest imports to the owning mirror-registry E2E subtree. This keeps fixture APIs out of production packages and prevents sibling product suites from coupling to product-specific setup. Promote a helper only after multiple product suites have a concrete, stable need for the same behavior.
Building out a suite
- Construct the production composition (
mirrorregistry.New) and serve itsApp.Handler(); do not build a parallel application stack for tests. - Exercise user-visible behavior over HTTP when routing, authentication, headers, redirects, or upload locations matter.
- Give each test an isolated database, storage directory, signing material, listener, and metrics state.
- Use ephemeral loopback ports, bounded clients,
t.Context(), andt.Cleanup(). - Drain HTTP work before closing the application and removing its temporary files.
- Keep clients and fixtures small. Add operations and options only for scenarios that exist.
- Leave process lifecycle, TLS installation, container behavior, and broad OCI conformance to their dedicated jobs.
Run the product suite with:
go test ./internal/e2e/mirrorregistry/...
go test -race ./internal/e2e/mirrorregistry/...