Skip to content

refactor(http): readability + maintainability sweep#64

Merged
jmgilman merged 5 commits into
masterfrom
session-038/http-sweep
May 27, 2026
Merged

refactor(http): readability + maintainability sweep#64
jmgilman merged 5 commits into
masterfrom
session-038/http-sweep

Conversation

@jmgilman
Copy link
Copy Markdown
Contributor

Summary

Fourth pass of the multi-session refactor sweep (after access/ PR #61, authz/ PR #62, exchange/ PR #63). Same 10-criteria bar applied to http/{auth,facts,compose}. Non-goals: bugs, correctness, performance.

The headline architectural fix: compose.PrincipalAuthenticatorSpec is demoted from interface to function type — it had exactly one production implementation (accessJWTAuthenticatorSpec), a premature port by the rule established in earlier passes. Consumers (testkit/) compile unchanged because the call-site literal is identical either way.

Changes per commit

  1. refactor(compose): demote PrincipalAuthenticatorSpec to a function typeaccessJWTAuthenticatorSpec and its method disappear; AccessJWT returns a closure; buildPrincipalAuthenticators calls spec() instead of spec.BuildPrincipalAuthenticator(). Test fixture principalAuthenticatorSpecFunc deleted (it existed solely to wrap a func into the old interface).
  2. refactor(http): add godocs and security/defensive inline comments — godocs on http/auth/options.go (options, defaultOptions) and http/compose/http.go (buildPrincipalAuthenticators); inline comments on RequireAuthorization's two ErrInternal branches (nil extractor and extractor failure are caller-side contract violations, not authorization denials) and buildPrincipalAuthenticators' two nil checks. Matches access/middleware/authenticator.go style.
  3. test(http): split shared helpers into helpers_test.gohttp/auth/helpers_test.go and http/compose/helpers_test.go created with the shared constants, helpers, and the fakes that will survive commit 4. Mechanical lift only.
  4. test(http): migrate root-port fakes to mockery — add authkit.PrincipalAuthenticator and authkit.Authorizer to .mockery.yaml, regenerate, swap hand-rolled fakes for authkitmocks.PrincipalAuthenticator / authkitmocks.Authorizer. Tests whose short-circuit path never reaches the authorizer use an unused authkitmocks.NewAuthorizer(t) (no expectations set) so a stray Can() would panic the test.
  5. chore(http): apply golines wrapping to long test linesgolangci-lint fmt (golines formatter) wrapped a few long lines the mockery migration introduced.

Test plan

  • moon run root:check --summary minimal — format, lint, build, unit, Testcontainers integration. All green.
  • go test ./http/... -count=1 -v — all 18 tests pass; one redundant test was already absent (none dropped this pass).
  • Public API surface: PrincipalAuthenticatorSpec is now a function type. Consumer source (testkit/internal/{authflow,httpui}) compiles unchanged because []compose.PrincipalAuthenticatorSpec{compose.AccessJWT(...)} works either way.
  • go list -f '{{.Name}}' ./http/{auth,facts,compose} — all three packages list cleanly.

Deliberately deferred

  • http/facts query/queryKey helpers — feature gap, not a readability defect; the package is opt-in and zero-consumer.
  • CI drift-detection for .mockery.yaml — still unwired. Worth a dedicated task once a few more entries land.

🤖 Generated with Claude Code

jmgilman added 5 commits May 27, 2026 15:29
The Spec interface had exactly one production implementation
(accessJWTAuthenticatorSpec) — a premature port by the rule established
in PRs #61 and #62 (no port without a second implementation). Replace
with a function type so AccessJWT returns a closure and the
accessJWTAuthenticatorSpec struct + BuildPrincipalAuthenticator method
disappear. buildPrincipalAuthenticators now calls spec() instead of
spec.BuildPrincipalAuthenticator().

Consumers (testkit/internal/authflow/runtime.go) compile unchanged:
[]compose.PrincipalAuthenticatorSpec{compose.AccessJWT(...)} works
identically whether PrincipalAuthenticatorSpec is an interface or a
function type. The test fixture principalAuthenticatorSpecFunc, which
existed solely to wrap a func into the interface, is removed; tests
now pass inline closures.
Godocs:
- http/auth/options.go: options (private struct) and defaultOptions
  (private function), matching the PR #62 bar.
- http/compose/http.go: buildPrincipalAuthenticators (private function).

Inline comments — match the access/middleware/authenticator.go:76-88
style. Each comment names what the check defends against and why the
error envelope is what it is, not what the code does:

- http/auth/middleware.go::RequireAuthorization: annotate the
  extract == nil branch and the wrapped extractor-failure branch.
  Both report ErrInternal because they are caller-side construction
  bugs (nil extractor) or caller-supplied function bugs (extraction
  error), not authorization denials. ErrInternal maps to 500 in the
  default renderer; ErrUnauthorized maps to 403. Choosing one over
  the other affects the public API contract; the choice is worth
  spelling out at the call site.
- http/compose/http.go::buildPrincipalAuthenticators: annotate the
  two nil branches (nil spec, nil-authenticator-from-non-erroring-spec)
  as contract violations that abort composition.

No new comments where the code already reads obviously (defaultErrorRenderer
status mapping, empty-struct context key, per-helper nil-request guards
in http/facts/facts.go). Package godocs already cover those.
Mechanical lift only — no test logic changes.

http/auth/helpers_test.go: newMiddleware, newTestPipeline,
newTestPipelineWithAuthorizer, newTestPipelineWithPrincipalAuthenticator,
newTestPipelineWithOptions, assertAuthenticationContext, testAuthentication,
testPrincipal, testResource.

http/compose/helpers_test.go: newTestPrincipalAuthenticator,
requestWithBearer, newAccessJWTIssuerAndVerifier.

The hand-rolled testPrincipalAuthenticator, testAuthorizer, and the
allow/deny/failingPrincipalAuthenticator + allowAuthorizer constructors
intentionally stay in middleware_test.go and http_test.go for this commit.
They get replaced by mockery in the next commit and the lift would be
churn.

Matches the helpers_test.go precedent from PRs #61 (access/jwt) and #63
(exchange).
Add authkit.PrincipalAuthenticator and authkit.Authorizer to .mockery.yaml.
The generated mocks land at mocks/authkit/principal_authenticator.go and
mocks/authkit/authorizer.go in package authkitmocks. Both are root-package
ports that http/auth and http/compose tests faked by hand — twice over.

http/auth/middleware_test.go and http/auth/helpers_test.go:
- Delete hand-rolled testPrincipalAuthenticator and testAuthorizer types.
- Delete allow/deny/failingPrincipalAuthenticator and allowAuthorizer
  constructor helpers.
- Replace per-test with authkitmocks.PrincipalAuthenticator and
  authkitmocks.Authorizer; keep typed helpers
  (newAllowPrincipalAuthenticator, newDenyPrincipalAuthenticator,
  newFailingPrincipalAuthenticator) in helpers_test.go so the
  Name()/AuthenticatePrincipal expectation patterns stay in one place.
- Tests whose short-circuit path never reaches the authorizer now pass
  an unused authkitmocks.NewAuthorizer(t) (no expectations set); a stray
  Can() call would panic the test.

http/compose/http_test.go and http/compose/helpers_test.go:
- Delete hand-rolled testPrincipalAuthenticator and testAuthorizer.
- Delete newTestPrincipalAuthenticator factory.
- Migrate the five tests that drove behaviour through the hand-rolled
  fakes to authkitmocks; the two AccessJWT integration tests keep their
  real-implementation chain and gain an unused authkitmocks.NewAuthorizer
  for the slot the pipeline does not exercise.

Matches the access/middleware (PR #61), authz/role (PR #62), and exchange
(PR #63) migration shape.
golangci-lint's golines formatter (configured in .golangci.yml) wraps a
few long lines that the mockery migration introduced. Pure formatting;
no logic change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant