Skip to content

refactor(exchange): readability + maintainability sweep#63

Merged
jmgilman merged 6 commits into
masterfrom
session-038/exchange-sweep
May 27, 2026
Merged

refactor(exchange): readability + maintainability sweep#63
jmgilman merged 6 commits into
masterfrom
session-038/exchange-sweep

Conversation

@jmgilman
Copy link
Copy Markdown
Contributor

Summary

Third pass of the multi-session refactor sweep (after access/ PR #61 and authz/ PR #62). Lifts exchange/ to the unified 10-criteria bar: godocs everywhere, self-explanatory names, consistent domain vocabulary, behaviour categorised behind contracts, helpful inline comments on security/defensive checks, small bespoke public API, hexagonal ports/adapters, mockery-based testing, pure domain logic separation, logical file organisation.

Non-goals: bugs, correctness, performance. Public API surface and runtime behaviour unchanged.

Changes per commit

  1. refactor(exchange): colocate request/result/options with their exchanger — split types.go so apitoken.go and identity.go each hold the shapes that configure them; extract the shared AccessTokenIssuer port into a new tokens.go; delete types.go.
  2. refactor(exchange): extract error helpers into errors.go — move exchangeError, unauthenticated, isContextError out of apitoken.go into a new errors.go so identity.go (which already calls exchangeError) sees them in a neutral location; godoc each with its sentinel-preservation contract.
  3. refactor(exchange): tighten port godocs and annotate defensive checks — expand APITokenVerifier and AccessTokenIssuer godocs; annotate the ErrPrincipalNotFound existence-leak branch in APITokenExchanger.Exchange, the nil/empty-ID branches in IdentityExchanger.Exchange, and the sentinel-passthrough branches in exchangeError. Matches the inline-comment style from access/middleware/authenticator.go (PR refactor(access): readability + maintainability sweep + introduce mockery #61).
  4. test(exchange): split shared helpers into helpers_test.go — mechanical lift of constants, helpers, and the two adapter-local fakes. Matches the precedent from access/jwt/helpers_test.go and authz/casbin/helpers_test.go.
  5. test(exchange): migrate root-port fakes to mockery — add authkit.PrincipalResolver to .mockery.yaml, regenerate, swap fakePrincipalFinder/fakeIdentityResolver for authkitmocks.PrincipalFinder/authkitmocks.PrincipalResolver. Adapter-local ports (APITokenVerifier, AccessTokenIssuer) stay as hand-rolled fakes per the established pattern. Drop TestAPITokenExchangerDoesNotRequireIdentityLink (assertions identical to the happy-path test); keep the one happy-path integration test wired against real memory.Store + apikey.Service + jwt.Issuer/Verifier as the end-to-end anchor.
  6. chore(exchange): drop trailing blank line introduced by file rewrite — pure format fix that gofmt flagged after commit 1.

Deliberately deferred

  • APITokenRequest.Plaintext rename — waits for the proof/apikey/ pass so the input field name realigns with whatever apikey.IssuedToken.Plaintext becomes (Token or Secret), avoiding mid-sweep asymmetry.

Test plan

  • moon run root:check --summary minimal — format, lint, build, unit, Testcontainers integration. All green.
  • go test ./exchange/... -count=1 -v — 12 tests pass; dropped one redundant test.
  • Public API surface byte-identical to pre-PR master (go doc github.com/meigma/authkit/exchange unchanged).
  • No consumer updates required (testkit/internal/{authflow,httpui} unchanged).

🤖 Generated with Claude Code

jmgilman added 6 commits May 27, 2026 14:41
Move APITokenVerifier, APITokenOptions, APITokenRequest, and APITokenResult
into apitoken.go alongside APITokenExchanger. Move IdentityOptions,
IdentityRequest, and IdentityResult into identity.go alongside
IdentityExchanger. Extract the shared AccessTokenIssuer port into a new
tokens.go. Delete types.go.

Public surface, behaviour, and error wording unchanged.
Move exchangeError, unauthenticated, and isContextError from apitoken.go
into a new errors.go so identity.go (which also calls exchangeError) sees
them in a neutral location. Add godocs documenting the sentinel-preservation
contract and the context-error fast path.
Expand APITokenVerifier and AccessTokenIssuer godocs to spell out the
sentinel-error contract and the signing/expiry responsibility. Annotate the
ErrPrincipalNotFound branch in APITokenExchanger.Exchange and the nil/empty-
ID branches in IdentityExchanger.Exchange to name what each check defends
against (existence-leak, downstream-authorization key). Annotate the two
early-return branches in exchangeError so the sentinel-preservation strategy
is visible at the call site.

Matches the access/middleware/authenticator.go inline-comment style from
PR #61.
Lift testPrincipalID, testTokenID, fixedTime, newAccessJWTIssuerAndVerifier,
newAPITokenExchanger, newIdentityExchanger, testExchangeIdentity, and
testExchangePrincipal into a new helpers_test.go. Move the two adapter-local
fakes (fakeAPITokenVerifier, fakeAccessTokenIssuer) alongside them since
both test files reference them.

Mechanical lift; no test logic changes. Matches the helpers_test.go
precedent from PR #61 (access/jwt) and PR #62 (authz/casbin).
Add authkit.PrincipalResolver to .mockery.yaml and regenerate; the generated
mock lands at mocks/authkit/principal_resolver.go in package authkitmocks.

Swap fakePrincipalFinder for authkitmocks.PrincipalFinder in apitoken_test.go
and fakeIdentityResolver for authkitmocks.PrincipalResolver in
identity_test.go. Both hand-rolled types are deleted. fakeAPITokenVerifier
and fakeAccessTokenIssuer stay because they back package-local adapter
ports (the established mockery pattern).

Drop TestAPITokenExchangerDoesNotRequireIdentityLink: its assertions are a
strict subset of TestAPITokenExchangerExchangesTokenForAccessJWT (both create
a principal with no identity link and exchange) and the test name promised
a stronger property than the body actually verifies.

Keep TestAPITokenExchangerExchangesTokenForAccessJWT integration-style with
real memory.Store + real apikey.Service so one anchor still proves the full
chain end-to-end.
A trailing blank line slipped into apitoken.go during the file-reorg commit
and gofmt flags it. Pure formatting fix; 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