Skip to content

chore(api): type AuthServiceInterface user and group writes with the auth.API* request and response types - #1261

Draft
IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-169
Draft

IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-169

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Root cause

AuthServiceInterface (internal/api/types.go) declared the four user and group write methods with any request and response types:

CreateUserAPI(ctx, actorUserID string, req any) (any, error)
UpdateUserAPI(ctx, actorUserID, userID string, req any) (any, error)
CreateGroupAPI(ctx, actorUserID string, req any) (any, error)
UpdateGroupAPI(ctx, actorUserID, groupID string, req any) (any, error)

Each auth.Service implementation started with a type assertion that returned fmt.Errorf("invalid request type") on a mismatch. A caller passing the wrong type therefore failed at runtime (as a 500) instead of at compile time. The comment in internal/auth/service_api.go said any was there "to avoid import cycles with the api package". That reason no longer holds: internal/api already imports internal/auth (auth.PermissionConstraints, auth.APIPermission and others appear in the same interface), so the interface can name the auth.API* types directly. No types need to move.

Fix

Signatures only; no behavior change.

  • internal/api/types.go: the four methods now take auth.APICreateUserRequest / auth.APIUpdateUserRequest / auth.APICreateGroupRequest / auth.APIUpdateGroupRequest. They return *auth.APICreateUserResponse / *auth.APIUser / *auth.APIGroup / *auth.APIGroup. These are exactly the dynamic types the service already returned.
  • internal/auth/service_api.go: the implementations take the concrete types, the four type assertions and their "invalid request type" errors are gone, and the stale import-cycle comment is corrected.
  • internal/server/app.go: the authServiceAdapter signatures are updated. The bodies are unchanged pass-throughs.
  • Test doubles updated: MockAuthService (mocks_test.go), mockAuthForExchange (handler_ri_exchange_test.go), realGroupAuth (group_explicit_zero_cap_test.go) and adminUserUpdateAuth (handler_users_update_race_integration_test.go, integration tag).

The handlers did not change. They already decode into these structs and pass the result straight to the JSON encoder, so the encoder receives the same values as before.

Test changes that fall out of the type change

  • MockAuthService now type-asserts its canned return values, the same way it does for GetUser. Several handler tests returned map[...] fixtures, which can no longer satisfy the interface. They now return the real auth.API* types. TestHandler_createUser_Success had also been returning *auth.APIUser where the real service returns *auth.APICreateUserResponse; the stronger mock types caught that drift.
  • internal/auth/service_api_test.go: the four "invalid request type" subtests are removed because that code path is now a compile error. The result.(*T) assertions become direct field access.
  • internal/server/adapter_test.go: TestAuthServiceAdapter_CreateUserAPI / _CreateGroupAPI / _UpdateGroupAPI passed map[string]interface{}. They only ever reached the type-assertion error, and they ended in _ = err. They now send typed requests and assert the store write and the returned value.

Wire format: proved byte-identical

New internal/api/user_group_wire_format_test.go:

  • TestUserGroupWrites_WireFormat drives Handler.HandleRequest (body decode → router → handler → interface → json.Marshal) for POST /api/users, PUT /api/users/{id}, POST /api/groups and PUT /api/groups/{id}. For each request it asserts:

    The test file is identical before and after the refactor: sha256 b91f2ee923024cbb16ebe5d936a12c1dc8aaac2382ad6d84705bf5039086fb58 on both sides, and it passes on both.

  • TestAuthServiceInterface_UserGroupWritesAreTyped uses reflection to pin the four methods' request and response types. It is the fail-before/pass-after guard against a regression back to any.

Typed-nil note: the real service returned a typed *APIUser / *APIGroup inside any before this change too, so even a hypothetical nil-pointer success would encode the same (null) on both sides. Only the old mock could return an untyped nil.

Closes #169

Verification

Unit, mock and fixture evidence on this branch (7d75eb53). The env for every command was AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true.

Fail-before, on unmodified main with only the new test file added:

$ go test ./internal/api/ -run 'TestUserGroupWrites_WireFormat|TestAuthServiceInterface_UserGroupWritesAreTyped' -count=1 -v
--- PASS: TestUserGroupWrites_WireFormat (all 4 subtests PASS: wire format recorded on main)
    expected: "auth.APICreateUserRequest"   actual: "interface {}"
    expected: "*auth.APICreateUserResponse" actual: "interface {}"
    ... (same for UpdateUserAPI, CreateGroupAPI, UpdateGroupAPI)
--- FAIL: TestAuthServiceInterface_UserGroupWritesAreTyped

Pass-after, on this branch with the same test file (same sha256):

$ go test ./internal/api/ -run 'TestUserGroupWrites_WireFormat|TestAuthServiceInterface_UserGroupWritesAreTyped' -count=1 -v
--- PASS: TestUserGroupWrites_WireFormat (4/4 subtests)
--- PASS: TestAuthServiceInterface_UserGroupWritesAreTyped

Other checks:

go build ./...                                       # ok
go vet ./...                                         # clean
go vet -tags integration ./...                       # clean
go test -count=1 ./internal/api/                     # ok
go test -count=1 ./internal/auth/                    # ok
go test -count=1 ./internal/server/                  # ok
go test -count=1 ./internal/mocks/                   # ok
go test -tags integration -count=1 -timeout 20m -run 'TestAdminUpdateUserHTTPKeepsConcurrentMFA' ./internal/api/   # PASS (real Postgres via testcontainers; its fixture signature changed)
golangci-lint v2.10.1 run --allow-parallel-runners ./internal/api/... ./internal/auth/... ./internal/server/...   # 0 issues
golangci-lint v2.10.1 run --allow-parallel-runners --build-tags integration ./internal/api/...                     # 0 issues
gocyclo -over 10 -ignore "_test\.go" internal/api/types.go internal/auth/service_api.go internal/server/app.go     # none

No DB, migration or concurrency change, so no new race test is needed.

Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree: queue/platform @ 936996ad = origin/main a14dc8f2 plus these heads, merged in this order:

Branch Tested head
fix/issue-337 ff3420c
fix/issue-169 7d75eb5
fix/issue-342 66b221f
fix/issue-310 5e11c21
fix/issue-326 5f0ea59
fix/issue-243 365e959
fix/issue-343 6c94036
fix/issue-312 d2848a8
fix/issue-316 acd60dd
fix/issue-339 75ea9b9
fix/issue-339-commitment-type 73bdb19
fix/issue-353 5d25fcd
fix/issue-313 3156bc4
fix/issue-357 53d9e58
fix/issue-368 0ba727d
fix/issue-419 61648c9
fix/issue-111 b04c372
fix/issue-686 0ad90b7
fix/issue-674 793095d
fix/issue-1055 4ec96f1
fix/issue-559 dfa9525
fix/issue-558 034eed4
fix/issue-874 1720539
fix/issue-666 1e120da
fix/issue-668 fb26345
fix/issue-568 ce04173
fix/issue-311 e54f250
fix/issue-177 0cbb88a
fix/issue-151 c21377c
fix/issue-166 41fb77b
fix/issue-27 2d67622
fix/issue-323 69288ae
fix/issue-59 36463e8
fix/issue-43 35e4069
fix/issue-158 6f76f8a
fix/issue-104 bb00491
fix/issue-55 971ef1b
fix/issue-361 89ac220
fix/issue-120 b458719
fix/issue-36 42bac6e
fix/issue-525 57c97dd
fix/issue-712 e5bcda0
fix/issue-72 6cc63ed

fix/issue-72 and fix/issue-312 conflict on one line in internal/api/handler_dashboard.go (both change the calculateCommitmentMetrics call). The tree used #72's side with #312's validated provider variable; whichever PR merges second takes that same resolution.

Linux, Go 1.26.9, AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers:

  • Passed at 936996ad: go build ./..., go vet ./..., go vet -tags integration ./... and the full go test -race -short -count=1 ./....
  • Static checks at 936996ad: golangci-lint v2.10.1 found 0 issues on every changed package, with and without --build-tags integration. tests/e2e is a separate module, so it was vetted under its own e2e tag. gocyclo -over 10 is silent on the changed non-test files.
  • Integration tests, every changed package, one package at a time (-tags integration -count=1, 60-minute local timeout because the VM was shared):
    • At 936996ad, these pass: internal/server, internal/credentials, internal/scheduler, internal/database/postgres/migrations, internal/purchase and internal/api.
    • internal/config passes except TestPostgresStoreDB_ResolveStaleRIExchange_ValidatesCloudCheck, which fails the same way on unmodified main (fixed by fix(config): compare resolve checked_at at the database's microsecond precision #1252).
    • These were run on the first build of this tree (26795376, same branches at earlier heads): internal/analytics, internal/auth, internal/commitmentopts, internal/database and cmd/lambda/clear-rate-limit all pass. The later fixes don't touch those packages.
  • What the combined run caught, each fixed on its own branch before this PR opened:
    • a compile break between fix/issue-104's test and fix/issue-326's httpToLambdaRequest signature change;
    • fix/issue-316 rejecting legacy-keyed plans;
    • fix/issue-120 versus main's global-filter test.
  • Secrets: git-secrets, using the repo's scripts/setup-git-secrets.sh patterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged. scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.
  • Nothing was applied to any cloud.

Out of scope (deliberately left)

The issue scopes the four user and group write methods, and that is what this PR changes. Other AuthServiceInterface methods still use any and could get the same treatment in a follow-up:

  • ListUsersAPI, GetGroupAPI, ListGroupsAPI and GetUserPermissionsAPI (response only). handler_auth.go and handler_registrations.go still type-assert the latter two results, and the comment there still cites the import cycle.
  • CreateAPIKeyAPI (request and response), ListUserAPIKeysAPI, GetAPIKeysUsageStatsAPI and ValidateUserAPIKeyAPI ((any, any, error)), which middleware.go and handler_auth.go type-assert.

Labels

Could a maintainer please mirror the issue's triage labels onto this PR: impact/internal, type/chore, triaged, priority/p3, severity/low, urgency/eventually, effort/m.

🤖 Generated with Claude Code

…auth.API* request and response types

CreateUserAPI, UpdateUserAPI, CreateGroupAPI and UpdateGroupAPI took and
returned any, and each service implementation opened with a type
assertion that failed with "invalid request type" at runtime. The import
cycle that once justified any is gone (api already imports auth), so the
interface, auth.Service, the server adapter and every test double now use
the concrete request types and *APICreateUserResponse / *APIUser /
*APIGroup responses. A wrong request type is now a compile error.

No behavior change: the handlers decode into the same structs and return
the same values to the same JSON encoder. A new HandleRequest-level test
pins the four endpoints' request values and response bytes; it passes
unchanged on both sides of this commit. The three adapter tests that
passed maps (and so only ever hit the type-assertion error) now send typed
requests and assert the store write and response.

Closes LeanerCloud#169

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added impact/internal Team-internal only type/chore Maintenance / non-user-visible triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline effort/m Days labels Oct 23, 2026
@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(api): replace any with concrete request types in AuthServiceInterface

1 participant