Skip to content

refactor: 401 and 403 tests#4099

Closed
Flo4604 wants to merge 1 commit intomainfrom
refactor/40X_tests
Closed

refactor: 401 and 403 tests#4099
Flo4604 wants to merge 1 commit intomainfrom
refactor/40X_tests

Conversation

@Flo4604
Copy link
Member

@Flo4604 Flo4604 commented Oct 14, 2025

What does this PR do?

This change standardises the tests for 401 and 403 tests, while keeping the options to still do custom tests for any endpoint.

This heavily reduces the boilerplate needed to setup tests for endpoints

Fixes #3543

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

Tests

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

@changeset-bot
Copy link

changeset-bot bot commented Oct 14, 2025

⚠️ No Changeset found

Latest commit: e3ea319

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Oct 14, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
dashboard Ready Ready Preview Comment Oct 14, 2025 8:00pm
engineering Ready Ready Preview Comment Oct 14, 2025 8:00pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

📝 Walkthrough

Walkthrough

Introduces a reusable authorization test helper framework (Test401 and Test403) in go/pkg/testutil/authz/ to reduce boilerplate across API route tests. Refactors approximately 50 test files across v2 API routes to use the new helpers, replacing manual harness setup, route construction, and HTTP header orchestration with declarative configuration-driven test invocations.

Changes

Cohort / File(s) Summary
Authorization Test Helpers (Core)
go/pkg/testutil/authz/authz.go, go/pkg/testutil/authz/types.go, go/pkg/testutil/authz/README.md
Introduces generic Test401 and Test403 helpers for authentication/authorization testing. Adds configuration types (PermissionTestConfig, PermissionTestCase, TestResources, Auth401TestConfig) to parameterize test setup, resource creation, request building, and permission validation. Includes helper functions for generating standard permission test cases and exploring permission combinations.
401 Tests — APIs
go/apps/api/routes/v2_apis_create_api/401_test.go, go/apps/api/routes/v2_apis_delete_api/401_test.go, go/apps/api/routes/v2_apis_get_api/401_test.go, go/apps/api/routes/v2_apis_list_keys/401_test.go
Replaced manual harness/route setup and explicit HTTP header scenarios with single authz.Test401 calls, removing per-case subtests and delegating authentication error validation to the helper.
403 Tests — APIs
go/apps/api/routes/v2_apis_create_api/403_test.go, go/apps/api/routes/v2_apis_delete_api/403_test.go, go/apps/api/routes/v2_apis_get_api/403_test.go, go/apps/api/routes/v2_apis_list_keys/403_test.go
Replaced explicit permission test matrices with authz.Test403 config, removing manual workspace/API/key creation and replacing multiple subtests with unified authorization check driven by required permissions and test resources.
401 Tests — Identities
go/apps/api/routes/v2_identities_create_identity/401_test.go, go/apps/api/routes/v2_identities_delete_identity/401_test.go, go/apps/api/routes/v2_identities_get_identity/401_test.go, go/apps/api/routes/v2_identities_list_identities/401_test.go, go/apps/api/routes/v2_identities_update_identity/401_test.go
Refactored authentication tests to use generic authz.Test401 helper, removing in-test route/harness construction and explicit subtests. Test function names renamed to TestAuthenticationErrors for consistency.
403 Tests — Identities
go/apps/api/routes/v2_identities_create_identity/403_test.go, go/apps/api/routes/v2_identities_delete_identity/403_test.go, go/apps/api/routes/v2_identities_get_identity/403_test.go, go/apps/api/routes/v2_identities_list_identities/403_test.go, go/apps/api/routes/v2_identities_update_identity/403_test.go
Replaced manual permission/DB setup with authz.Test403 config. Renamed test functions to TestAuthorizationErrors, removed explicit workspace/identity creation, and consolidated multiple subtests into parameterized authorization check.
401 Tests — Keys (Permissions/Roles)
go/apps/api/routes/v2_keys_add_permissions/401_test.go, go/apps/api/routes/v2_keys_add_roles/401_test.go, go/apps/api/routes/v2_keys_remove_permissions/401_test.go, go/apps/api/routes/v2_keys_remove_roles/401_test.go, go/apps/api/routes/v2_keys_set_permissions/401_test.go, go/apps/api/routes/v2_keys_set_roles/401_test.go
Replaced extensive in-test harness/route setup and multi-case subtests with single authz.Test401 calls, removing explicit HTTP header construction and delegating 401 validation to the helper.
403 Tests — Keys (Permissions/Roles)
go/apps/api/routes/v2_keys_add_permissions/403_test.go, go/apps/api/routes/v2_keys_add_roles/403_test.go, go/apps/api/routes/v2_keys_remove_permissions/403_test.go, go/apps/api/routes/v2_keys_remove_roles/403_test.go, go/apps/api/routes/v2_keys_set_permissions/403_test.go, go/apps/api/routes/v2_keys_set_roles/403_test.go
Refactored to use authz.Test403 with configuration-driven resource setup, removing manual DB inserts and permission validation, consolidating multiple subtests into single authorized/unauthorized test flow.
401 Tests — Keys (Lifecycle)
go/apps/api/routes/v2_keys_create_key/401_test.go, go/apps/api/routes/v2_keys_delete_key/401_test.go, go/apps/api/routes/v2_keys_get_key/401_test.go, go/apps/api/routes/v2_keys_reroll_key/401_test.go, go/apps/api/routes/v2_keys_update_key/401_test.go, go/apps/api/routes/v2_keys_verify_key/401_test.go
Replaced manual test harness and explicit per-case authentication scenarios with authz.Test401, removing direct HTTP header construction and consolidating test logic into single helper invocation. Function names standardized to TestAuthenticationErrors.
403 Tests — Keys (Lifecycle)
go/apps/api/routes/v2_keys_create_key/403_test.go, go/apps/api/routes/v2_keys_delete_key/403_test.go, go/apps/api/routes/v2_keys_get_key/403_test.go, go/apps/api/routes/v2_keys_reroll_key/403_test.go, go/apps/api/routes/v2_keys_update_key/403_test.go
Refactored to use authz.Test403 with config-driven handler/resource/request setup. Removed manual API/key/workspace creation and permission matrices; added support for cross-workspace and cross-API permission scenarios via SetupResources. Test functions renamed to TestAuthorizationErrors.
401 Tests — Keys (Other)
go/apps/api/routes/v2_keys_update_credits/401_test.go, go/apps/api/routes/v2_keys_whoami/401_test.go
Replaced granular multi-case unauthorized tests with single authz.Test401 invocation, removing explicit subtests and delegating authentication error handling to the helper.
403 Tests — Keys (Other)
go/apps/api/routes/v2_keys_update_credits/403_test.go
Refactored to use authz.Test403 config-driven approach, removing manual setup and consolidating test scenarios into unified authorization check.
401 Tests — Permissions/Roles
go/apps/api/routes/v2_permissions_create_permission/401_test.go, go/apps/api/routes/v2_permissions_create_role/401_test.go, go/apps/api/routes/v2_permissions_delete_permission/401_test.go, go/apps/api/routes/v2_permissions_delete_role/401_test.go, go/apps/api/routes/v2_permissions_get_permission/401_test.go, go/apps/api/routes/v2_permissions_get_role/401_test.go, go/apps/api/routes/v2_permissions_list_permissions/401_test.go, go/apps/api/routes/v2_permissions_list_roles/401_test.go
Replaced manual route/harness setup and explicit per-case authentication tests with single authz.Test401 calls, removing direct HTTP header handling and delegating to centralized helper.
403 Tests — Permissions/Roles
go/apps/api/routes/v2_permissions_create_permission/403_test.go, go/apps/api/routes/v2_permissions_create_role/403_test.go, go/apps/api/routes/v2_permissions_delete_permission/403_test.go, go/apps/api/routes/v2_permissions_delete_role/403_test.go, go/apps/api/routes/v2_permissions_get_permission/403_test.go, go/apps/api/routes/v2_permissions_get_role/403_test.go, go/apps/api/routes/v2_permissions_list_permissions/403_test.go, go/apps/api/routes/v2_permissions_list_roles/403_test.go
Refactored to use authz.Test403 with configuration-driven resource setup and authorization checks, removing manual DB seeding and permission validation subtests. Test functions renamed to TestAuthorizationErrors.
401 Tests — Rate Limit
go/apps/api/routes/v2_ratelimit_delete_override/401_test.go, go/apps/api/routes/v2_ratelimit_get_override/401_test.go, go/apps/api/routes/v2_ratelimit_limit/401_test.go, go/apps/api/routes/v2_ratelimit_list_overrides/401_test.go, go/apps/api/routes/v2_ratelimit_set_override/401_test.go
Replaced explicit harness/route setup and per-case authentication scenarios with single authz.Test401 calls, removing direct HTTP header construction and delegating to centralized helper.
403 Tests — Rate Limit
go/apps/api/routes/v2_ratelimit_delete_override/403_test.go, go/apps/api/routes/v2_ratelimit_get_override/403_test.go, go/apps/api/routes/v2_ratelimit_limit/403_test.go, go/apps/api/routes/v2_ratelimit_list_overrides/403_test.go, go/apps/api/routes/v2_ratelimit_set_override/403_test.go
Refactored to use authz.Test403 with config-driven handler/resource setup, removing manual namespace/override creation and direct route invocation, consolidating permission scenarios into unified test flow.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test Function
    participant Helper as authz.Test401/Test403
    participant Harness as testutil.Harness
    participant Route as Handler Route
    participant KeyManager as Key Manager
    
    Test->>Helper: Call Test401/Test403 with config
    Helper->>Harness: Create test harness
    Helper->>Route: Register route via SetupHandler
    Route->>KeyManager: Initialize with DB, Keys, Logger
    
    rect rgb(200, 220, 255)
    Note over Helper: For each permission scenario (401 or 403)
    Helper->>KeyManager: Create root key with specific permissions
    Helper->>Route: Build Authorization header
    Helper->>Route: Issue HTTP request
    Route->>Route: Check authorization
    Helper->>Helper: Assert Status and Body match expected
    end
    
    Helper-->>Test: Test results
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Core helper implementation (authz.go, types.go): Moderate complexity with generic types, permission combination logic, and multiple test case generators; requires careful verification of test case generation and assertion patterns.
  • High homogeneity across test files: ~50 test refactors follow nearly identical patterns (remove manual setup, add helper call, update imports), reducing per-file review time despite large file count.
  • Areas requiring extra attention:
    • authz.go: The generateStandardPermissionTests and testPermissionCombinations logic to ensure permission scenarios are properly enumerated and tested.
    • v2_keys_create_key/403_test.go, v2_ratelimit_* tests: Complex SetupResources implementations with cross-workspace/cross-API scenarios and error handling.
    • Verification that test coverage equivalence is maintained between old explicit tests and new parameterized helper approach.
    • Confirm that test resources (workspace, API, key, identity, permission, role, namespace, override) are properly initialized in all SetupResources implementations.

Possibly related PRs

Suggested labels

testing, refactoring, test-helpers

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title "refactor: 401 and 403 tests" clearly and concisely describes the primary change in this pull request. It directly identifies that the PR focuses on refactoring authorization test patterns (401 for authentication, 403 for authorization), which aligns with the changeset that consolidates these test patterns across 40+ endpoint test files. The title is specific enough to convey the main purpose without being overly verbose.
Linked Issues Check ✅ Passed The PR directly addresses the requirements in linked issue #3543. The issue requested consolidating repetitive 401/403 test logic into reusable functions that accept permission parameters to reduce boilerplate. This PR accomplishes this by introducing authz.Test401 and authz.Test403 generic helper functions, creating supporting types (PermissionTestConfig, TestResources, PermissionTestCase), and then systematically refactoring 40+ test files across multiple endpoint packages to use these standardized helpers. The refactored tests now pass route factories, permission lists, and resource setup functions to centralized test helpers, eliminating per-endpoint test duplication while maintaining the ability to customize tests via AdditionalPermissionTests.
Out of Scope Changes Check ✅ Passed All changes in this PR are narrowly scoped to the objective of consolidating test patterns. The changeset includes only test file refactoring (40+ endpoint test files converted to use new helpers), new test utility files (authz.go, types.go, and README.md in go/pkg/testutil/authz/), and no modifications to production code or unrelated functionality. The go.mod reference appears to be a documentation artifact rather than a material dependency change. No out-of-scope changes such as bug fixes, feature additions outside the test consolidation scope, or unrelated refactoring were detected.
Description Check ✅ Passed The PR description covers the essential required sections from the template: it explains what the PR does (standardizing 401/403 tests and reducing boilerplate), correctly identifies the type of change as a Chore, provides a reference to the linked issue (#3543), and includes the standard checklist. While the "How should this be tested" section is minimal (stating only "Tests"), the description is substantially complete with the core information about the PR's purpose and intent clearly communicated.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/40X_tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Collaborator

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 28, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.


require.Equal(t, tc.expectedStatus, res.Status,
"expected %d, got %d, body: %s", tc.expectedStatus, res.Status, res.RawBody)
require.NotNil(t, res.Body)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we also assert some of the body content? ie error.status, error.title and error.type?

//
// func TestCreateApiForbidden(t *testing.T) {
// authz.Test403(t,
// authz.PermissionTestConfig[handler.Request, handler.Response]{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the setup is inconsistent between Test401 and Test 403.
401 accepts multiple arguments and 403 accepts a struct

I don't care that much, but can we make it the same?

// For each required permission, generate related test cases
for _, perm := range requiredPermissions {
parts := strings.Split(perm, ".")
if len(parts) >= 3 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should just fail if len(parts) is not 3. Cause that very likely means we have an issue somewhere, right now we're silently ignoring it

// },
// )
// }
func Test401[TReq any, TRes any](
Copy link
Collaborator

@chronark chronark Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"technically" this is authN and doesn't fit in a package called authz

it's a nit but I'd just rename the package to auth

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (33)
go/apps/api/routes/v2_permissions_get_role/403_test.go (3)

13-13: Enable test parallelization.

Add t.Parallel() for faster suites, if the Harness is concurrency-safe.

 func TestAuthorizationErrors(t *testing.T) {
+	t.Parallel()
 	authz.Test403(t,

Please confirm the Harness and seeded resources are safe under parallel execution.


31-33: Replace "role_id" magic string with a file‑scoped const.

Reduces typo risk and improves readability.

@@
-				return authz.TestResources{
+				return authz.TestResources{
 					WorkspaceID: h.Resources().UserWorkspace.ID,
 					Custom: map[string]string{
-						"role_id": roleID,
+						roleIDKey: roleID,
 					},
 				}
@@
-			CreateRequest: func(res authz.TestResources) handler.Request {
+			CreateRequest: func(res authz.TestResources) handler.Request {
 				return handler.Request{
-					Role: res.Custom["role_id"],
+					Role: res.Custom[roleIDKey],
 				}
 			},

Add near the imports:

const roleIDKey = "role_id"

Also applies to: 38-39


23-23: Permission string is correct and matches handler guard; string literal in test setup is acceptable but could use shared constant for consistency.

The handler correctly verifies rbac.*.read_role via rbac.T(rbac.Tuple{ResourceType: rbac.Rbac, ResourceID: "*", Action: rbac.ReadRole}) (lines 56–62 of handler.go). The test permission string aligns perfectly with this requirement.

While constants Rbac and ReadRole exist in go/pkg/rbac/permissions.go, the test setup uses a raw string literal. For test consistency with the handler's constant-based approach, consider creating a test helper or using the same Tuple pattern, though this is a minor refactoring—string literals for test setup are idiomatic.

go/apps/api/routes/v2_permissions_delete_permission/401_test.go (1)

12-28: Consider standardizing test names across all 401 test files.

The test naming is inconsistent across the refactored files: some use TestAuthenticationErrors while others (e.g., v2_ratelimit_list_overrides/401_test.go, v2_ratelimit_delete_override/401_test.go) use TestUnauthorizedAccess. Standardizing to one name would improve consistency.

go/apps/api/routes/v2_ratelimit_limit/401_test.go (1)

12-12: Enable parallel execution if harness supports it.

Add t.Parallel() at the top of the test to speed up CI.

 func TestUnauthorizedAccess(t *testing.T) {
+	t.Parallel()
 	authz.Test401[handler.Request, handler.Response](t,

Please confirm testutil.Harness and shared fixtures are safe for parallel tests.

go/apps/api/routes/v2_permissions_delete_permission/403_test.go (2)

13-13: Run test in parallel if possible.

Add t.Parallel() to improve throughput (assuming harness is concurrency-safe).

 func TestAuthorizationErrors(t *testing.T) {
+	t.Parallel()
 	authz.Test403(t,

If the harness uses shared DB state, confirm isolation before enabling.


24-24: Create a constant for the full permission string.

The raw string "rbac.*.delete_permission" should be replaced with a shared constant to prevent drift and typos. No centralized constant currently exists for the full permission strings (only DeletePermission ActionType = "delete_permission" for the action part).

Consider defining full permission constants in go/pkg/rbac/permissions.go alongside the ActionType constants, e.g.:

const DeletePermissionFull = "rbac.*.delete_permission"

Then use it here and in other test files that reference the same permission string.

go/apps/api/routes/v2_identities_list_identities/401_test.go (1)

12-12: Enable parallelism.

Add t.Parallel() for faster test runs if fixtures are parallel-safe.

 func TestAuthenticationErrors(t *testing.T) {
+	t.Parallel()
 	authz.Test401[handler.Request, handler.Response](t,
go/apps/api/routes/v2_identities_get_identity/403_test.go (2)

13-13: Parallelize the test if safe.

Add t.Parallel() to speed up CI (validate harness isolation first).

 func TestAuthorizationErrors(t *testing.T) {
+	t.Parallel()
 	authz.Test403(t,

23-24: Use centralized permission constants to prevent string drift.

The hard-coded string "identity.*.read_identity" appears in multiple test files without centralization. Create a constant combining the Identity resource type and ReadIdentity action (e.g., IdentityReadIdentity = "identity.*.read_identity") in go/pkg/rbac/permissions.go, then reference it in place of the string literal.

go/apps/api/routes/v2_permissions_get_role/401_test.go (1)

12-12: Enable test parallelism.

Add t.Parallel() if the harness is concurrency-safe.

 func TestAuthenticationErrors(t *testing.T) {
+	t.Parallel()
 	authz.Test401[handler.Request, handler.Response](t,
go/apps/api/routes/v2_keys_set_roles/401_test.go (1)

14-30: Nice 401 refactor; KeyCache wiring and payload look right. Add t.Parallel.

  • Dependencies sufficient; request meets validation.
  • Minor: run in parallel.
 func TestAuthenticationErrors(t *testing.T) {
-	authz.Test401[handler.Request, handler.Response](t,
+	t.Parallel()
+	authz.Test401[handler.Request, handler.Response](t,
go/apps/api/routes/v2_permissions_list_roles/403_test.go (1)

13-27: Permission identifier confirmed as canonical; consider t.Parallel() for test optimization.

The "rbac.*.read_role" permission matches the canonical form used throughout the codebase. The 403 test structure is correct and requires no resource setup. Consider adding t.Parallel() to parallelize test execution if not already parallelized in a parent suite:

 func TestAuthorizationErrors(t *testing.T) {
+	t.Parallel()
 	authz.Test403(t,
go/apps/api/routes/v2_permissions_create_role/403_test.go (1)

13-30: Add t.Parallel() for consistency with test patterns in the same directory.

Config is clean and permission usage is correct. Sibling tests in go/apps/api/routes/v2_keys_* consistently use t.Parallel() as the first statement in test functions to enable parallel execution.

 func TestAuthorizationErrors(t *testing.T) {
+	t.Parallel()
 	authz.Test403(t,
 		authz.PermissionTestConfig[handler.Request, handler.Response]{
 			SetupHandler: func(h *testutil.Harness) zen.Route {
go/apps/api/routes/v2_keys_reroll_key/401_test.go (1)

13-29: Test401 helper properly validates response contracts; migration approved. Consider adding t.Parallel() for test optimization.

The helper validates both status codes (401 and 400 cases) and response body structure via typed parsing to openapi.UnauthorizedErrorResponse. The 7 test scenarios comprehensively cover authentication failures with no regressions from prior behavior.

Optional: Add t.Parallel() at line 14 to parallelize test execution.

go/apps/api/routes/v2_keys_set_permissions/401_test.go (1)

14-30: Good migration to authz.Test401; wiring looks correct.

Route factory and minimal request are appropriate for 401-path coverage; dependencies are sensibly provided.

If you find yourself repeating the same handler wiring across multiple key-related tests, consider a tiny local helper to reduce duplication.

go/apps/api/routes/v2_keys_remove_permissions/401_test.go (1)

14-30: Consistent and clean 401 helper usage.

Wiring and minimal request are appropriate; no issues spotted.

Consider a small shared builder for the key handlers across 401 tests to DRY up repeated wiring.

go/apps/api/routes/v2_identities_get_identity/401_test.go (1)

12-26: Minor: enable parallelism and rely on type inference.

  • Add t.Parallel() for faster suites (if Harness is parallel-safe).
  • Type args can be inferred; drop explicit [handler.Request, handler.Response].

Please confirm Harness isolation is safe for t.Parallel and that Test401 covers both “missing Authorization header” and “malformed/unsupported scheme” cases.

 func TestAuthenticationErrors(t *testing.T) {
-	authz.Test401[handler.Request, handler.Response](t,
+	t.Parallel()
+	authz.Test401(t,
 		func(h *testutil.Harness) zen.Route {
 			return &handler.Handler{
 				DB:     h.DB,
 				Keys:   h.Keys,
 				Logger: h.Logger,
 			}
 		},
 		func() handler.Request {
 			return handler.Request{
 				Identity: "test_identity",
 			}
 		},
 	)
 }
go/apps/api/routes/v2_ratelimit_set_override/403_test.go (2)

29-59: Reduce key typos: constantize Custom keys.

Avoid stringly-typed map keys across SetupResources/CreateRequest.

 func TestWorkspacePermissions(t *testing.T) {
-	authz.Test403(t,
+	authz.Test403(t,
 		authz.PermissionTestConfig[handler.Request, handler.Response]{
 			SetupHandler: func(h *testutil.Harness) zen.Route {
 				return &handler.Handler{
 					DB:                      h.DB,
 					Keys:                    h.Keys,
 					Logger:                  h.Logger,
 					Auditlogs:               h.Auditlogs,
 					RatelimitNamespaceCache: h.Caches.RatelimitNamespace,
 				}
 			},
 			RequiredPermissions: []string{"ratelimit.*.set_override"},
 			SetupResources: func(h *testutil.Harness) authz.TestResources {
+				const keyNamespaceID = "namespace_id"
 				ctx := context.Background()
 
 				// Create a namespace
 				namespaceID := uid.New(uid.RatelimitNamespacePrefix)
 				err := db.Query.InsertRatelimitNamespace(ctx, h.DB.RW(), db.InsertRatelimitNamespaceParams{
 					ID:          namespaceID,
 					WorkspaceID: h.Resources().UserWorkspace.ID,
 					Name:        uid.New("name"),
 					CreatedAt:   time.Now().UnixMilli(),
 				})
 				if err != nil {
 					t.Fatalf("failed to create namespace: %v", err)
 				}
 
 				return authz.TestResources{
 					WorkspaceID: h.Resources().UserWorkspace.ID,
 					Custom: map[string]string{
-						"namespace_id": namespaceID,
+						keyNamespaceID: namespaceID,
 					},
 				}
 			},
 			CreateRequest: func(res authz.TestResources) handler.Request {
+				const keyNamespaceID = "namespace_id"
 				return handler.Request{
-					Namespace:  res.Custom["namespace_id"],
+					Namespace:  res.Custom[keyNamespaceID],
 					Identifier: "test_identifier",
 					Limit:      10,
 					Duration:   1000,
 				}
 			},
 		},
 	)
 }

16-28: Add t.Parallel() to align with codebase test conventions.

The permission slug "ratelimit.*.set_override" is confirmed across OpenAPI specs, tests, and implementation files. However, this test lacks t.Parallel() despite widespread adoption of this pattern in other tests within the same directory (e.g., v2_keys_reroll_key, v2_keys_update_key, v2_keys_create_key), suggesting the testutil.Harness infrastructure supports parallel execution. Add t.Parallel() at the start of TestWorkspacePermissions to align with project conventions.

go/apps/api/routes/v2_ratelimit_limit/403_test.go (2)

3-10: Align test package naming with the rest for consistency.

Other tests use package handler_test; consider renaming v2RatelimitLimit_test to handler_test to avoid mixed package names in the same module.

-package v2RatelimitLimit_test
+package handler_test

26-47: Split scenarios: existing vs. non‑existing namespace.

Comment notes create_namespace is needed only when namespace doesn’t exist. Consider a second test that seeds the namespace and sets RequiredPermissions to only "ratelimit.*.limit" to verify both paths.

I can draft a variant that seeds the namespace and asserts 403 with missing "ratelimit.*.limit" only.

go/apps/api/routes/v2_ratelimit_get_override/403_test.go (1)

28-71: Constantize Custom keys and enable parallelism.

Avoid key typos and speed up tests.

 func TestWorkspacePermissions(t *testing.T) {
-	authz.Test403(t,
+	t.Parallel()
+	authz.Test403(t,
 		authz.PermissionTestConfig[handler.Request, handler.Response]{
 			SetupHandler: func(h *testutil.Harness) zen.Route {
 				return &handler.Handler{
 					DB:                      h.DB,
 					Keys:                    h.Keys,
 					Logger:                  h.Logger,
 					RatelimitNamespaceCache: h.Caches.RatelimitNamespace,
 				}
 			},
 			RequiredPermissions: []string{"ratelimit.*.read_override"},
 			SetupResources: func(h *testutil.Harness) authz.TestResources {
+				const (
+					keyNamespaceID = "namespace_id"
+					keyOverrideID  = "override_id"
+				)
 				ctx := context.Background()
 
 				// Create a namespace
 				namespaceID := uid.New(uid.RatelimitNamespacePrefix)
 				err := db.Query.InsertRatelimitNamespace(ctx, h.DB.RW(), db.InsertRatelimitNamespaceParams{
 					ID:          namespaceID,
 					WorkspaceID: h.Resources().UserWorkspace.ID,
 					Name:        uid.New("test"),
 					CreatedAt:   time.Now().UnixMilli(),
 				})
 				if err != nil {
 					t.Fatalf("failed to create namespace: %v", err)
 				}
 
 				// Create an override
 				overrideID := uid.New(uid.RatelimitOverridePrefix)
 				err = db.Query.InsertRatelimitOverride(ctx, h.DB.RW(), db.InsertRatelimitOverrideParams{
 					ID:          overrideID,
 					WorkspaceID: h.Resources().UserWorkspace.ID,
 					NamespaceID: namespaceID,
 					Identifier:  "test_identifier",
 					Limit:       10,
 					Duration:    1000,
 					CreatedAt:   time.Now().UnixMilli(),
 				})
 				if err != nil {
 					t.Fatalf("failed to create override: %v", err)
 				}
 
 				return authz.TestResources{
 					WorkspaceID: h.Resources().UserWorkspace.ID,
 					Custom: map[string]string{
-						"namespace_id": namespaceID,
-						"override_id":  overrideID,
+						keyNamespaceID: namespaceID,
+						keyOverrideID:  overrideID,
 					},
 				}
 			},
 			CreateRequest: func(res authz.TestResources) handler.Request {
+				const keyNamespaceID = "namespace_id"
 				return handler.Request{
-					Namespace:  res.Custom["namespace_id"],
+					Namespace:  res.Custom[keyNamespaceID],
 					Identifier: "test_identifier",
 				}
 			},
 		},
 	)
 }
go/apps/api/routes/v2_keys_add_roles/403_test.go (1)

25-25: Add AdditionalPermissionTests for single-permission scenarios.

The authorization test helper generates test cases for "no permissions" and wrong actions per-permission, but does not test subsets of multi-permission requirements. Add explicit AdditionalPermissionTests for:

  • only "api.*.update_key"
  • only "rbac.*.add_role_to_key"

to verify that each individual permission is required and not sufficient alone.

go/apps/api/routes/v2_keys_reroll_key/403_test.go (1)

59-70: Add a positive “recoverable key” case.

You cover the 403 when encrypt_key is missing. Also assert the pass path (create_key + encrypt_key on the recoverable key) to prevent future regressions. This likely fits as an additional “combination” in the helper rather than a manual test here.

go/apps/api/routes/v2_keys_delete_key/403_test.go (2)

69-112: De-duplicate the cross-API test; move it into the helper.

This manual subtest repeats harness setup and overlaps with what the helper can cover. Prefer expressing it via AdditionalPermissionTests if the helper supports dynamic permission strings (e.g., “api.{OtherApiID}.delete_key”) derived from TestResources. Consider extending PermissionTestCase to allow building permissions from resources (e.g., BuildPermissions func(res TestResources) []string), then drop this block.


3-12: Prune fmt import after removing the manual subtest.

Once the manual block is gone, fmt becomes unused.

 import (
-	"fmt"
 	"testing"
go/pkg/testutil/authz/authz.go (2)

268-292: Add a “specific resource permission” pass case.

When RequiredPermissions contain a wildcard and ApiID is present, also test the specific-id variant should pass.

 	testCases := []struct {
 		name        string
 		permissions []string
 		shouldPass  bool
 	}{
 		{
 			name:        "exact required permission",
 			permissions: config.RequiredPermissions,
 			shouldPass:  true,
 		},
+		// If wildcard, also verify specific resource id permission passes
+	}
+
+	// Append specific-id pass case when applicable
+	if resources.ApiID != "" {
+		specific := make([]string, 0, len(config.RequiredPermissions))
+		for _, p := range config.RequiredPermissions {
+			parts := strings.Split(p, ".")
+			if len(parts) >= 3 && parts[1] == "*" {
+				parts[1] = resources.ApiID
+				specific = append(specific, strings.Join(parts, "."))
+			}
+		}
+		if len(specific) > 0 {
+			testCases = append(testCases, struct {
+				name        string
+				permissions []string
+				shouldPass  bool
+			}{
+				name:        "specific resource permission",
+				permissions: specific,
+				shouldPass:  true,
+			})
+		}
 	}

174-204: Enable dynamic permissions in custom 403 cases (optional).

To eliminate route‑local manual tests (e.g., delete_key), consider extending PermissionTestCase to support building permissions from TestResources (e.g., BuildPermissions func(res TestResources) []string). Runner would prefer BuildPermissions over static Permissions when provided.

go/pkg/testutil/authz/types.go (4)

52-56: Document nil semantics for hooks.

Make it explicit that nil is acceptable and what happens.

Doc-only change:

-// SetupResources optionally sets up test data before running tests.
-// Returns resource IDs that can be used in CreateRequest and permission checks.
-// If nil, uses default resources from harness.Resources().
+// SetupResources optionally sets up test data before running tests.
+// Returns resource IDs that can be used in CreateRequest and permission checks.
+// If nil, the harness default (harness.Resources()) is used.
@@
-// ModifyRequest optionally modifies the request for this specific test
+// ModifyRequest optionally modifies the request for this specific test.
+// If nil, the original request is used unmodified.

Also applies to: 69-71


61-77: Consider exposing typed error validation.

If many endpoints return structured errors, allowing a typed validator can reduce parsing boilerplate later.

Option (non-breaking, additive):

 type PermissionTestCase[TReq any] struct {
@@
 	// ValidateError optionally validates the error response
 	ValidateError func(t *testing.T, errorDetail string)
+	// ValidateResponse optionally validates the full raw body.
+	// Ignored if nil; keep errorDetail for simple cases.
+	ValidateResponse func(t *testing.T, body []byte)
 }

This keeps string-based checks while enabling richer assertions when needed.


19-24: Refactor struct fields to use Go initialism convention: ApiID → APIID, OtherApiID → OtherAPIID.

This refactor aligns with Go style guidelines for initialisms (API, ID). The struct fields are defined in types.go (lines 19–24) and are used throughout test files in go/apps/api/routes/*/ and in authz.go (line 239). Update all usages to match the renamed fields.

-	// ApiID is the primary API ID for testing
-	ApiID string
+	// APIID is the primary API ID for testing
+	APIID string

-	// OtherApiID is a secondary API for cross-resource testing
-	OtherApiID string
+	// OtherAPIID is a secondary API for cross-resource testing
+	OtherAPIID string

Affected locations: go/pkg/testutil/authz/types.go, go/pkg/testutil/authz/authz.go:239, all test files initializing this struct in go/apps/api/routes/*/, and go/pkg/testutil/authz/README.md.


40-59: Based on my verification, I can now generate the rewritten review comment. The core finding is that TRes is indeed unused in both struct definitions and the test helper functions, confirming the review comment's central concern.


TRes is unused: document the intent or remove it.

Both PermissionTestConfig and Auth401TestConfig declare TRes but never use it in any struct field. Similarly, Test401 has TRes in its signature but doesn't use it, and Test403 receives it via config but the config never references it.

If TRes is retained for future extensibility or API design, add a comment to each type clarifying this. Otherwise, remove it:

-type PermissionTestConfig[TReq any, TRes any] struct {
+type PermissionTestConfig[TReq any] struct {
-type Auth401TestConfig[TReq any, TRes any] struct {
+type Auth401TestConfig[TReq any] struct {
-func Test401[TReq any, TRes any](
+func Test401[TReq any](
-func Test403[TReq any, TRes any](
+func Test403[TReq any](

Note: This requires updating all instantiation sites across the codebase (30+ test files currently pass handler.Response as the unused second argument).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c764837 and e3ea319.

📒 Files selected for processing (71)
  • go/apps/api/routes/v2_apis_create_api/401_test.go (1 hunks)
  • go/apps/api/routes/v2_apis_create_api/403_test.go (1 hunks)
  • go/apps/api/routes/v2_apis_delete_api/401_test.go (1 hunks)
  • go/apps/api/routes/v2_apis_delete_api/403_test.go (1 hunks)
  • go/apps/api/routes/v2_apis_get_api/401_test.go (1 hunks)
  • go/apps/api/routes/v2_apis_get_api/403_test.go (1 hunks)
  • go/apps/api/routes/v2_apis_list_keys/401_test.go (1 hunks)
  • go/apps/api/routes/v2_apis_list_keys/403_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_create_identity/401_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_create_identity/403_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/401_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/403_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_get_identity/401_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_get_identity/403_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_list_identities/401_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_list_identities/403_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_update_identity/401_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_update_identity/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_permissions/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_permissions/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_roles/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_roles/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_create_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_create_key/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_delete_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_delete_key/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_get_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_get_key/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_permissions/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_roles/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_roles/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_reroll_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_reroll_key/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_permissions/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_key/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_verify_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_whoami/401_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_create_permission/401_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_create_permission/403_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_create_role/401_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_create_role/403_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_delete_permission/401_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_delete_permission/403_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_delete_role/401_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_delete_role/403_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_get_permission/401_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_get_permission/403_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_get_role/401_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_get_role/403_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_list_permissions/401_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_list_permissions/403_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_list_roles/401_test.go (1 hunks)
  • go/apps/api/routes/v2_permissions_list_roles/403_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_delete_override/401_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_delete_override/403_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_get_override/401_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_get_override/403_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_limit/401_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_limit/403_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_list_overrides/401_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_list_overrides/403_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_set_override/401_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_set_override/403_test.go (1 hunks)
  • go/pkg/testutil/authz/README.md (1 hunks)
  • go/pkg/testutil/authz/authz.go (1 hunks)
  • go/pkg/testutil/authz/types.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-14T16:25:48.167Z
Learnt from: Flo4604
PR: unkeyed/unkey#3785
File: go/apps/api/routes/v2_keys_reroll_key/401_test.go:52-61
Timestamp: 2025-08-14T16:25:48.167Z
Learning: User Flo4604 requested creation of a GitHub issue to track converting all test files to use table-driven test patterns as a broader codebase improvement, following the suggestion made during review of go/apps/api/routes/v2_keys_reroll_key/401_test.go.

Applied to files:

  • go/apps/api/routes/v2_keys_add_permissions/401_test.go
  • go/apps/api/routes/v2_keys_update_credits/401_test.go
  • go/apps/api/routes/v2_keys_create_key/401_test.go
🔇 Additional comments (68)
go/apps/api/routes/v2_permissions_get_role/403_test.go (2)

8-10: Imports look good.

New testutil/authz, seed, and zen imports are correct and are used.


13-42: Good migration to the shared authz harness.

Clear reduction in boilerplate and centralized 403 behavior. Matches the PR objective to standardize 401/403 tests.

go/apps/api/routes/v2_apis_list_keys/403_test.go (1)

15-65: Test correctly validates all permission boundaries for the route.

The 403 test properly covers:

  • Missing api.*.read_key permission
  • Missing api.*.read_api permission
  • Attempting decrypt without api.*.decrypt_key permission (conditional check when Decrypt=true)

Handler enforcement verified: decrypt permission is only checked when req.Decrypt is true, matching the test's conditional case exactly.

go/apps/api/routes/v2_permissions_create_role/401_test.go (1)

8-9: Handler dependencies verified; refactor is sound.

All required services (Logger, DB, Keys, Auditlogs) are properly declared in the Handler struct and correctly wired in the test. The Handle() method implementation only uses these four declared fields—no Vault, KeyCache, or ApiCache references found. The auth helper migration is clean and complete.

go/apps/api/routes/v2_keys_create_key/403_test.go (1)

15-57: Encrypt_key enforcement verified—no issues found.

The handler correctly enforces the encrypt_key permission when Recoverable=true. At handler.go lines 144–152, the code calls auth.VerifyRootKey with rbac.EncryptKey action within the encryption block, returning an error (403) if the permission is missing. The test case correctly validates this behavior.

go/apps/api/routes/v2_identities_update_identity/401_test.go (1)

13-34: Excellent refactoring!

The migration to the centralized authz.Test401 helper successfully reduces boilerplate while maintaining test coverage. The route and request factories are properly structured and provide appropriate test data.

go/apps/api/routes/v2_permissions_create_permission/401_test.go (1)

12-29: LGTM!

The refactoring correctly applies the centralized testing pattern with appropriate route and request factories.

go/apps/api/routes/v2_ratelimit_list_overrides/401_test.go (1)

12-27: Refactoring looks good.

The implementation correctly uses the centralized helper pattern.

go/apps/api/routes/v2_ratelimit_delete_override/401_test.go (1)

13-31: LGTM!

The helper integration is correct, and the route factory properly includes the cache dependency.

go/apps/api/routes/v2_permissions_list_permissions/401_test.go (1)

12-25: LGTM!

The empty request factory is appropriate for a list endpoint that doesn't require specific identifiers.

go/apps/api/routes/v2_keys_add_roles/401_test.go (1)

13-31: LGTM!

The refactoring correctly implements the helper pattern with appropriate test data for the roles endpoint.

go/apps/api/routes/v2_apis_delete_api/401_test.go (1)

13-30: LGTM!

The helper integration is correct with appropriate test data for the API deletion endpoint.

go/apps/api/routes/v2_permissions_delete_permission/401_test.go (1)

12-28: LGTM!

The refactoring correctly applies the helper pattern for the permission deletion endpoint.

go/apps/api/routes/v2_ratelimit_get_override/401_test.go (1)

13-29: LGTM! Clean refactoring to centralized auth helper.

The test correctly uses the authz.Test401 helper with appropriate handler setup and request construction. The use of uid.New("test") for the namespace is appropriate for generating a valid test identifier.

go/apps/api/routes/v2_permissions_create_permission/403_test.go (1)

12-32: LGTM! Appropriate configuration for permission creation.

The test correctly uses authz.Test403 with the required permission "rbac.*.create_permission". The absence of SetupResources is appropriate here since permission creation likely only requires workspace context, which is provided by the default test harness.

go/apps/api/routes/v2_ratelimit_set_override/401_test.go (1)

13-32: LGTM! Proper 401 test setup for set override endpoint.

The handler setup appropriately includes Auditlogs (needed for state-modifying operations) and the request factory correctly provides all required fields (Namespace, Identifier, Limit, Duration) for setting a rate limit override.

go/apps/api/routes/v2_apis_delete_api/403_test.go (1)

13-43: LGTM! Well-structured authorization test for API deletion.

The test properly sets up resources (creates an API to delete), specifies the correct permission ("api.*.delete_api"), and constructs a request that references the created API. The handler configuration appropriately includes Caches for potential cache invalidation during deletion.

go/apps/api/routes/v2_permissions_get_permission/401_test.go (1)

12-27: LGTM! Concise unauthorized access test.

The test appropriately uses authz.Test401 with minimal handler setup (suitable for a read operation) and a straightforward test permission identifier.

go/apps/api/routes/v2_apis_create_api/401_test.go (1)

12-32: LGTM! Well-documented unauthorized access test.

The test includes helpful documentation explaining its purpose and correctly uses authz.Test401 with appropriate handler setup (including Auditlogs for the creation operation).

go/apps/api/routes/v2_permissions_list_roles/401_test.go (1)

12-25: LGTM! Appropriate test for list endpoint.

The test correctly uses an empty request (appropriate for a list endpoint with no required parameters) and minimal handler setup for a read-only operation.

go/apps/api/routes/v2_keys_remove_roles/403_test.go (1)

26-63: ****

The test logic is correct and intentional. The handler implements idempotent role removal: it validates that requested roles exist in the workspace, but gracefully skips roles not attached to the key without error. The 200_test.go confirms this with an explicit test case "idempotent operation - removing non-assigned role", which validates this exact behavior. The 403_test.go correctly sets up an authorization test by creating a separate role and requesting its removal to verify the 403 permission check works regardless of attachment status.

Likely an incorrect or invalid review comment.

go/apps/api/routes/v2_ratelimit_limit/401_test.go (1)

13-33: LGTM: clean 401 harness wiring.

Route factory and request builder look correct and minimal for auth-only coverage.

go/apps/api/routes/v2_permissions_delete_permission/403_test.go (1)

16-43: LGTM: cohesive 403 config.

Handler setup, workspace-scoped seeding, and request mapping are consistent with the authz harness.

go/apps/api/routes/v2_identities_list_identities/401_test.go (1)

13-24: LGTM: minimal 401 test config.

Route factory and empty request are appropriate since auth fails before payload validation.

go/apps/api/routes/v2_identities_get_identity/403_test.go (1)

16-41: LGTM: 403 harness wiring and resource seeding.

Configuration is clear; workspace and identity setup align with RBAC checks.

go/apps/api/routes/v2_permissions_get_role/401_test.go (1)

13-26: LGTM: concise 401 coverage.

Route factory and request builder are appropriate for auth-only validation.

go/apps/api/routes/v2_apis_list_keys/401_test.go (1)

14-29: Verified: 401 test helper validates header semantics and error responses comprehensively.

The authz.Test401 helper performs extensive header validation (7 scenarios including malformed headers, missing auth, bearer spacing) and asserts response body is non-nil. Your test setup correctly delegates to this helper. Adding t.Parallel() is safe and optional since the test has no shared state.

go/apps/api/routes/v2_permissions_delete_role/401_test.go (1)

13-27: 401 test is correctly structured; parity verification concern was unfounded.

The test correctly uses authz.Test401, not Test403. These helpers have intentionally different signatures by design—Test401 accepts inline functions while Test403 uses a config struct—because they test different concerns (authentication vs. permission). Your minimal Request and Handler setup are appropriate for auth-only testing.

Adding t.Parallel() is optional and safe since the test is isolated.

go/apps/api/routes/v2_apis_get_api/401_test.go (1)

13-28: LGTM! Clean migration to centralized auth test helper.

The test correctly uses the authz.Test401 helper with appropriate handler dependencies and a valid request factory that generates a placeholder API ID.

go/apps/api/routes/v2_keys_get_key/401_test.go (1)

14-32: LGTM! Proper migration to the auth helper framework.

The test correctly migrates to authz.Test401 with all necessary handler dependencies (including Vault and Auditlogs) and creates a valid request with a placeholder KeyId and Decrypt flag.

go/apps/api/routes/v2_keys_whoami/401_test.go (1)

12-27: LGTM! Appropriate test setup for the whoami endpoint.

The test correctly uses the authz.Test401 helper with minimal handler dependencies and a placeholder invalid key string, which is appropriate for authentication error testing.

go/apps/api/routes/v2_keys_create_key/401_test.go (1)

13-31: LGTM! Well-documented placeholder usage.

The test correctly migrates to authz.Test401 with appropriate handler dependencies. The inline comment explaining the placeholder API ID is helpful for future maintainers.

go/apps/api/routes/v2_keys_add_permissions/403_test.go (1)

13-60: LGTM! Comprehensive 403 test configuration.

The test correctly uses authz.Test403 with appropriate:

  • Handler dependencies including KeyCache for verification
  • Required permissions covering both key updates and RBAC operations
  • Resource setup creating all necessary entities (API, Key, Permission)
  • Request construction using the created resources
go/apps/api/routes/v2_apis_get_api/403_test.go (1)

13-41: LGTM! Well-structured 403 test for read operations.

The test correctly uses authz.Test403 with:

  • Appropriate minimal handler dependencies for a read operation
  • Single required permission api.*.read_api
  • Simple resource setup creating just the API entity needed
  • Request construction using the created API ID
go/apps/api/routes/v2_permissions_list_permissions/403_test.go (1)

12-28: LGTM! Minimal configuration appropriate for list endpoint.

The test correctly uses authz.Test403 with the rbac.*.read_permission permission. The empty request and absence of SetupResources are appropriate for a list endpoint that operates at the workspace scope rather than requiring specific resource IDs.

go/apps/api/routes/v2_keys_delete_key/401_test.go (1)

13-29: LGTM on the 401 refactor.

Helper usage, handler wiring, and minimal request shape are consistent and clear.

go/apps/api/routes/v2_keys_remove_roles/401_test.go (1)

14-30: Looks good; helper-based 401 test is straightforward.

Minimal request plus standard wiring is sufficient for auth-path checks.

go/apps/api/routes/v2_apis_create_api/403_test.go (1)

27-33: All concerns verified; no issues found.

The test is correctly structured:

  • Permission string "api.*.create_api" is consistently used across test files and matches the handler's authorization requirements
  • Request requires only Name; WorkspaceID is correctly sourced from the auth context (not the request payload)
  • SetupResources is not needed for creation endpoints (only for operations on existing resources, like delete/update)

The suggestion to define permission strings as constants is a stylistic improvement rather than a requirement; the hardcoded values are currently consistent across the codebase.

go/apps/api/routes/v2_keys_update_key/401_test.go (1)

13-28: LGTM! Clean refactoring to use shared authentication test helper.

The test properly wires the handler dependencies and generates a valid test request. This refactoring successfully reduces boilerplate while maintaining test coverage.

go/apps/api/routes/v2_ratelimit_delete_override/403_test.go (1)

16-74: LGTM! Well-structured authorization test with proper resource setup.

The test correctly:

  • Wires all handler dependencies including the cache
  • Creates necessary test resources (namespace and override)
  • Uses the Custom map pattern to pass resource IDs to the request factory
  • Specifies the required permission for the operation

The refactoring successfully consolidates the authorization test logic into a configuration-driven pattern.

go/apps/api/routes/v2_identities_list_identities/403_test.go (1)

13-42: LGTM! Appropriate test structure for list endpoint.

The test correctly:

  • Seeds test identities to ensure permission checks are triggered
  • Uses the harness helper method for cleaner resource creation
  • Returns an empty request, which is appropriate for list endpoints
  • Includes a helpful comment explaining the resource setup rationale

The refactoring maintains test coverage while reducing boilerplate.

go/apps/api/routes/v2_identities_create_identity/401_test.go (1)

12-27: LGTM! Consistent refactoring to shared authentication helper.

The test properly:

  • Wires all handler dependencies (DB, Keys, Logger, Auditlogs)
  • Provides a valid test request with ExternalId
  • Follows the same clean pattern as other 401 tests in the PR
go/apps/api/routes/v2_identities_update_identity/403_test.go (1)

13-47: LGTM! Well-structured authorization test for identity updates.

The test correctly:

  • Uses the harness helper for cleaner identity creation
  • Stores the identity ID in the Custom map for request construction
  • Creates test metadata inline in the request factory
  • Properly passes metadata as a pointer to match the expected type
  • Specifies the required permission (identity.*.update_identity)

The refactoring successfully reduces boilerplate while maintaining clear test logic.

go/apps/api/routes/v2_identities_get_identity/401_test.go (1)

12-26: Nice migration to the shared 401 helper.

Good use of authz.Test401 with a minimal handler factory and request builder. LGTM.

go/apps/api/routes/v2_ratelimit_set_override/403_test.go (1)

16-60: 403 harness usage looks solid.

Handler wiring and resource seeding read clean. RequiredPermissions matches the operation. LGTM.

go/apps/api/routes/v2_ratelimit_limit/403_test.go (2)

13-49: Good 403 coverage with additional permission combinations.

Shared harness config is clear; AdditionalPermissionTests make intent explicit. LGTM.


12-35: <!-- [scratchpad_start]
[task review_file_1/1 verification_2/2]
Status: Complete

[observations]

  • StartAllServices() is intentionally a no-op - services managed externally via docker-compose
  • Containers run on fixed localhost addresses (MySQL:3306, Redis, ClickHouse)
  • All test harnesses connect to same external services
  • NEW HARNESS per test means potential database state conflicts if parallel
  • Comments in containers.go note "tests should create and use specific database names to avoid conflicts"

[critical_findings]

  1. Permission slugs ARE VALID:

    • "ratelimit..limit" and "ratelimit..create_namespace" confirmed in 24+ locations
    • Used consistently across codebase with no typos or variations
  2. t.Parallel() CONTRADICTS CODEBASE PATTERN:

    • 0 out of 34 403_test.go files use t.Parallel()
    • This is a CONSISTENT PATTERN, not accidental
    • Other tests (200_test.go, 400_test.go) in same directories DO use t.Parallel()
    • Suggests deliberate exclusion for authorization tests

[analysis]

  • Empirical evidence: 34/34 403_test.go files consistently avoid parallelization
  • External database services shared by all tests increase risk of state conflicts
  • authz.Test403() creates new harness each time, but harness connects to shared external DB
  • Pattern suggests sequential execution is deliberate for authorization tests
  • Original review comment contradicts established codebase convention

[conclusion]
Permission slugs: ✓ Valid
t.Parallel() suggestion: ✗ Contradicts established pattern across entire codebase
Recommendation: Update review comment to acknowledge pattern and note caution about parallelization

[done]
</scratchpad_end] -->

Confirm permission slugs are valid; note that adding t.Parallel() contradicts established codebase pattern.

The permission slugs "ratelimit..limit" and "ratelimit..create_namespace" are confirmed valid—they're used consistently across 24+ locations in tests, specs, documentation, and dashboard configuration with no variations.

However, adding t.Parallel() contradicts an established codebase pattern: all 34 existing 403_test.go files in go/apps/api/routes/ deliberately omit parallelization, despite other test files in the same directories (200_test.go, 400_test.go) using t.Parallel(). This suggests a deliberate convention for authorization tests, likely because test harnesses connect to shared external database services (MySQL, Redis, ClickHouse) that are centrally managed. Running authorization tests in parallel risks database state conflicts.

Before adding t.Parallel(), verify with the team whether this pattern should change or whether sequential execution remains intentional for authorization tests.

go/apps/api/routes/v2_ratelimit_get_override/403_test.go (2)

17-73: Harness config looks correct and readable.

Balanced SetupHandler/SetupResources/CreateRequest; LGTM.


27-28: Permission slug verified as correct.

The permission slug "ratelimit.*.read_override" is consistently defined and used throughout the codebase for reading rate limit overrides across Go tests, TypeScript implementations, OpenAPI specifications, and dashboard permission configurations. No discrepancies found.

go/apps/api/routes/v2_permissions_get_permission/403_test.go (2)

13-43: Clean refactor to shared 403 test.

Use of seed.CreatePermission keeps setup concise. LGTM.


23-41: Constantization is safe; permission slug is valid; t.Parallel() requires verification.

The refactoring suggestions are mostly sound:

  • Constantize "permission_id": Safe to apply. The string literal appears exactly twice (lines 33, 39) and consolidating to a const reduces duplication risk.
  • Permission slug "rbac.*.read_permission": Valid and confirmed across the codebase in multiple endpoints and test fixtures.
  • t.Parallel(): Unclear. While the test harness creates isolated resources per invocation (fresh containers, MySQL, Redis), note that tests in v2_permissions_* routes consistently omit t.Parallel() despite using isolated harnesses—unlike v2_keys_* tests which do use it. This suggests an intentional pattern specific to the authz test framework. Recommend verifying with the maintainers whether the authz.Test403 framework supports parallelization before adding it.
go/apps/api/routes/v2_ratelimit_list_overrides/403_test.go (3)

65-69: LGTM: request construction is correct for list-by-namespace.


26-26: Permission slug is correct and consistent.

The permission ratelimit.*.read_override at line 26 matches the canonical form used consistently throughout the codebase (tests, API routes, OpenAPI specs, and dashboard). No issues found.


27-40: No critical issues found; t.Fatalf concern is incorrect for this context, and seeding optimization is optional.

The SetupResources closure runs synchronously on the main test thread (authz.go line 159), making t.Fatalf safe. No separate goroutine executes it. Permission enforcement happens before Handler.Handle() is invoked, so the namespace and override seeding is only consumed if a test case passes authorization. Since the 403 test contains no AdditionalPermissionTests, all scenarios remain 403-only, making the seeding unnecessary—but this is an optimization choice, not a correctness issue. The permission slug "ratelimit.*.read_override" is correctly defined (rbac/permissions.go:92).

go/apps/api/routes/v2_permissions_delete_role/403_test.go (2)

16-23: LGTM: concise 403 harness usage; handler and request wiring look solid.

Also applies to: 25-43


24-24: Permission slug is correct and properly defined.

The permission "rbac..delete_role" is correctly constructed from the RBAC tuple format. The handler implementation uses rbac.Tuple{ResourceType: rbac.Rbac, ResourceID: "*", Action: rbac.DeleteRole}, which converts via the Tuple.String() method to produce exactly "rbac..delete_role". The DeleteRole action constant is properly defined, and the pattern is used consistently across tests and API specifications.

go/apps/api/routes/v2_keys_update_credits/401_test.go (2)

15-34: LGTM: clear 401 harness; request payload is minimal and valid.


15-34: No issues found—Test401 helper adequately covers all 401 variants.

The Test401 helper comprehensively exercises missing headers, invalid schemes/tokens, and signature validation: invalid bearer token (401), nonexistent key (401), malformed headers without Bearer prefix (400), Bearer-only (400), missing header (400), and empty header (400). The code in lines 15–34 correctly delegates to this reusable helper rather than writing bespoke tests, avoiding future coverage gaps.

go/apps/api/routes/v2_keys_add_roles/403_test.go (2)

16-24: LGTM: good use of shared 403 harness; resources and request are correctly wired.

Also applies to: 25-57


25-25: Permission slugs are correctly configured and enforced.

Both "api..update_key" and "rbac..add_role_to_key" are properly defined in the RBAC system (go/pkg/rbac/permissions.go), actively enforced by the handler via auth.VerifyRootKey(), and tested for authorization failures in the 403_test.go. The permissions follow the established pattern used across other similar routes (set_roles, remove_roles, add_permissions).

go/apps/api/routes/v2_keys_get_key/403_test.go (2)

14-25: LGTM: solid 403 coverage including decrypt-specific permutations; handler wiring looks correct.

Also applies to: 26-49, 50-69


26-26: Permission slugs confirmed as valid and consistently used throughout the system.

Both "api..read_key" and "api..decrypt_key" are properly registered as permissions in the RBAC system. They're created dynamically during testing and used consistently across test files, API routes, OpenAPI specifications, and dashboard components. The permission slug naming follows the system's established pattern ("api.{scope}.{action}") and matches policy expectations. No conflicts or inconsistencies found.

go/apps/api/routes/v2_keys_update_credits/403_test.go (1)

15-56: LGTM: consolidated 403 test is correct and complete.

Handler wiring, RequiredPermissions, seeded resources, and request body (Increment + nullable value) look consistent with the endpoint’s contract.

go/apps/api/routes/v2_keys_add_permissions/401_test.go (1)

14-30: LGTM: standardized 401 coverage via helper.

Good shift to the shared Test401 harness; minimal boilerplate and clear factories. Based on learnings.

go/apps/api/routes/v2_identities_create_identity/403_test.go (1)

12-30: LGTM: 403 helper usage matches route semantics.

Permission, handler wiring, and request are appropriate.

go/apps/api/routes/v2_keys_verify_key/401_test.go (1)

12-28: LGTM: concise 401 tests via helper.

Route factory and request are minimal and correct.

go/apps/api/routes/v2_keys_update_key/403_test.go (1)

13-48: LGTM: standardized 403 path.

Solid use of helper; resources include ApiID enabling cross-resource checks.

go/pkg/testutil/authz/types.go (1)

1-8: Good addition; clean, minimal surface.

Package, imports, and public API placement look solid for a shared testutil module.

Comment on lines +23 to +27
func() handler.Request {
return handler.Request{
Identity: uid.New("test"),
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the Identity UID prefix.

The request factory uses uid.New("test") for the Identity field. Verify that "test" is a valid UID prefix for identity objects, or if it should use a constant like uid.IdentityPrefix for consistency with other routes that use uid.APIPrefix and uid.KeyPrefix.


🏁 Script executed:

#!/bin/bash
# Description: Search for the correct identity UID prefix constant and verify usage patterns

# Search for IdentityPrefix constant definition
ast-grep --pattern 'const $_ = "id_"'

# Search for other identity UID generation patterns in test files
rg -n "Identity.*uid\.New" --type=go -A2 -B2

# Search for uid prefix constants
rg -n "Prefix.*=.*\"[a-z]+_\"" --type=go go/pkg/uid/

Length of output: 6538



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for uid package constants and their values

# Find the uid package definition file
fd -e go "uid" go/pkg/ --type f

# Look at the uid package constants
rg -n "const.*Prefix.*=" --type=go go/pkg/uid/ -A1

# Check specifically for TestPrefix and IdentityPrefix
rg -n "(TestPrefix|IdentityPrefix|IdentityPrefix|=\"test\")" --type=go go/pkg/uid/

Length of output: 244


Replace hardcoded "test" with uid.TestPrefix constant for consistency.

The code at line 25 uses uid.New("test") as a hardcoded string literal. The uid package defines TestPrefix = "test" for tests only. Use uid.New(uid.TestPrefix) instead to align with the uid package conventions and match the pattern used throughout other test files (e.g., line 62 in v2_identities_get_identity/400_test.go).

🤖 Prompt for AI Agents
In go/apps/api/routes/v2_identities_delete_identity/401_test.go around lines 23
to 27, replace the hardcoded uid.New("test") with uid.New(uid.TestPrefix) to use
the uid package's TestPrefix constant; update the handler.Request.Identity value
to call uid.New with uid.TestPrefix so tests follow the established convention
and remain consistent with other test files.

Comment on lines +24 to +30
RequiredPermissions: []string{"identity.*.delete_identity"},
CreateRequest: func(res authz.TestResources) handler.Request {
return handler.Request{
Identity: uid.New("test"),
}
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Validate permission key and identity ID; consider seeding if authz isn’t first.

  • Use the real identity prefix constant instead of a raw "test" prefix to avoid format validation failures.
  • Ensure the 403 path is exercised before any payload/resource validation. If not, seed a valid identity and pass its ID.

Suggested change (adjust the constant name to your actual one):

-                return handler.Request{
-                    Identity: uid.New("test"),
-                }
+                return handler.Request{
+                    Identity: uid.New(uid.IdentityPrefix),
+                }

Run to confirm the enforced permission name and whether the handler authorizes before validating the request, and to inspect the Request fields:


🏁 Script executed:

#!/bin/bash
# Check handler permission usage/naming
rg -nP -C2 '"identity\.\*\.[^"]*delete_identity"|Require(d)?Permissions|Authorize' go/apps/api/routes/v2_identities_delete_identity

# Inspect Request shape for this route
rg -nP -C3 'type\s+Request\s+struct' go/apps/api/routes/v2_identities_delete_identity

Length of output: 6788


🏁 Script executed:

#!/bin/bash
# Find uid package and identity prefix constant
rg -nP -C3 'IdentityPrefix|const.*Prefix' go/apps/api/routes/v2_identities_delete_identity

# Search in broader uid package
fd 'uid' -t d go/apps/api
rg -nP 'package uid|const.*Prefix' --type go

Length of output: 3368


🏁 Script executed:

#!/bin/bash
# Find Request struct definition in this route
fd -e go go/apps/api/routes/v2_identities_delete_identity -exec grep -l "type.*Request\s*struct" {} \;

# Look at handler registration/middleware
rg -nP 'RequiredPermissions|Middleware|Register' go/apps/api/routes/v2_identities_delete_identity/handler.go -A2 -B2

Length of output: 257


🏁 Script executed:

#!/bin/bash
# Check test resources setup in 200_test.go and compare with 403_test.go
rg -nP 'SetupResources|CreateRequest' go/apps/api/routes/v2_identities_delete_identity/200_test.go -A5 -B2
rg -nP 'SetupResources|CreateRequest' go/apps/api/routes/v2_identities_delete_identity/403_test.go -A5 -B2

Length of output: 236


🏁 Script executed:

#!/bin/bash
# Correct fd syntax - find files then search within them
fd '403_test.go|200_test.go' go/apps/api/routes/v2_identities_delete_identity

# Show full test case setup in 200_test.go
rg -nP 'func.*TestDeleteIdentity|SetupResources|CreateRequest' go/apps/api/routes/v2_identities_delete_identity/200_test.go -A10 | head -50

Length of output: 381


🏁 Script executed:

#!/bin/bash
# Find Request struct definition
rg -nP 'type\s+Request\s+struct' go/apps/api/routes/v2_identities_delete_identity -A10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if 403_test.go is missing SetupResources compared to 200_test.go
echo "=== 403_test.go structure ===" 
cat go/apps/api/routes/v2_identities_delete_identity/403_test.go | head -35

echo -e "\n=== 200_test.go first test case ===" 
rg -nP 'SetupResources|CreateRequest' go/apps/api/routes/v2_identities_delete_identity/200_test.go -B10 -A10 | head -40

Length of output: 906


Replace "test" prefix with uid.IdentityPrefix constant for proper UID formatting.

The code uses uid.New("test") but should use uid.New(uid.IdentityPrefix) to match the pattern in other tests within the same route (200_test.go:26 and 404_test.go:51, 87). The handler calls FindIdentity with the request identity, which validates UID format—using the correct prefix constant ensures proper formatting and consistency.

-                return handler.Request{
-                    Identity: uid.New("test"),
-                }
+                return handler.Request{
+                    Identity: uid.New(uid.IdentityPrefix),
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RequiredPermissions: []string{"identity.*.delete_identity"},
CreateRequest: func(res authz.TestResources) handler.Request {
return handler.Request{
Identity: uid.New("test"),
}
},
},
RequiredPermissions: []string{"identity.*.delete_identity"},
CreateRequest: func(res authz.TestResources) handler.Request {
return handler.Request{
Identity: uid.New(uid.IdentityPrefix),
}
},
🤖 Prompt for AI Agents
In go/apps/api/routes/v2_identities_delete_identity/403_test.go around lines 24
to 30, the test constructs a UID with uid.New("test") which uses a literal
string prefix; replace that with uid.New(uid.IdentityPrefix) so the Identity UID
has the proper prefix format used elsewhere. Update the CreateRequest to return
handler.Request{ Identity: uid.New(uid.IdentityPrefix) } to match other tests
and satisfy FindIdentity validation.

Comment on lines +59 to 96
// Cross-API permission test
t.Run("permission for different API", func(t *testing.T) {
// Create root key with create permission for other API
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, fmt.Sprintf("api.%s.create_key", otherApiID))

headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
h := testutil.NewHarness(t)

route := &handler.Handler{
DB: h.DB,
Keys: h.Keys,
Logger: h.Logger,
Auditlogs: h.Auditlogs,
Vault: h.Vault,
}
h.Register(route)

res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req)
require.Equal(t, 403, res.Status)
require.NotNil(t, res.Body)
})

t.Run("permission for specific API but requesting different API", func(t *testing.T) {
// Create root key with create permission for specific API
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, fmt.Sprintf("api.%s.create_key", otherApiID))
// Create two APIs
api := h.CreateApi(seed.CreateApiRequest{
WorkspaceID: h.Resources().UserWorkspace.ID,
})
otherApi := h.CreateApi(seed.CreateApiRequest{
WorkspaceID: h.Resources().UserWorkspace.ID,
})

headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
}
// Create root key with permission for otherApi only
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, fmt.Sprintf("api.%s.create_key", otherApi.ID))

// Try to create key for different API
res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req)
require.Equal(t, 403, res.Status)
require.NotNil(t, res.Body)
})

t.Run("unrelated permission", func(t *testing.T) {
// Create root key with completely unrelated permission
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, "workspace.read")

headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
}

res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req)
require.Equal(t, 403, res.Status)
require.NotNil(t, res.Body)
})

t.Run("partial permission match", func(t *testing.T) {
// Create root key with permission that partially matches but isn't sufficient
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, "api.create")

headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
}

res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req)
require.Equal(t, 403, res.Status)
require.NotNil(t, res.Body)
})

t.Run("create recoverable key without perms", func(t *testing.T) {
// Create root key with permission that partially matches but isn't sufficient
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, "api.*.create_key")

req := handler.Request{
ApiId: apiID,
Recoverable: ptr.P(true),
ApiId: api.ID,
}

headers := http.Header{
res := testutil.CallRoute[handler.Request, handler.Response](h, route, map[string][]string{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
}
}, req)

res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req)
require.Equal(t, 403, res.Status)
require.NotNil(t, res.Body)
if res.Status != 403 {
t.Errorf("expected 403, got %d, body: %s", res.Status, res.RawBody)
}
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Consolidate cross-API case into the authz helper (or extend helper to support dynamic permissions).

Mixing manual subtests with the helper defeats the standardization goal. Prefer expressing this as an AdditionalPermissionTest that grants permission for OtherApiID and targets ApiID. If the helper lacks a way to compute permissions from TestResources, consider extending PermissionTestCase with a BuildPermissions(res TestResources) []string hook so this scenario can live in the unified harness.


🏁 Script executed:

#!/bin/bash
# Check whether PermissionTestCase supports dynamic permissions today
rg -n 'type\s+PermissionTestCase' go/pkg/testutil/authz -C5
rg -n 'BuildPermissions|PermissionsFn' go/pkg/testutil/authz || true

Length of output: 748



---

🏁 Script executed:

```shell
#!/bin/bash
# Get the full PermissionTestCase struct definition and related code
rg -n 'type PermissionTestCase' go/pkg/testutil/authz -A20

Length of output: 1411



---

🏁 Script executed:

```shell
#!/bin/bash
# Find where AdditionalPermissionTests is used/executed in the authz helper
rg -n 'AdditionalPermissionTests' go/pkg/testutil/authz -B2 -A5

Length of output: 2418



---

🏁 Script executed:

```shell
#!/bin/bash
# Get the test execution loop that uses testCases and Permissions
rg -n 'for.*testCases' go/pkg/testutil/authz/authz.go -A40 | head -60

Length of output: 2067


Extend PermissionTestCase to support dynamic permission computation.

The authz helper currently lacks a way to build permissions dynamically from TestResources. While ModifyRequest already demonstrates callback-based extensibility, Permissions remains static. To consolidate the cross-API test into AdditionalPermissionTests, add a BuildPermissions func(TestResources) []string hook (optional, with static Permissions as fallback). This would allow the cross-API scenario—which grants permission for otherApi but targets api—to be expressed uniformly without manual subtests.

🤖 Prompt for AI Agents
In go/apps/api/routes/v2_keys_create_key/403_test.go around lines 59-96, the
test harness lacks a way to compute permissions dynamically from TestResources;
add an optional BuildPermissions func(TestResources) []string field to the
PermissionTestCase (falling back to the existing static Permissions slice when
nil), update the authz helper to call BuildPermissions(res) if present to
construct permissions for the test, and refactor the cross-API subtest into
AdditionalPermissionTests using this new hook so the test can grant permission
for otherApi while targeting api without manual subtests.

Comment on lines +61 to +67
Name: "decrypt permission without read permission",
Permissions: []string{"api.*.decrypt_key"}, // Has decrypt but not read
ModifyRequest: func(req handler.Request, res authz.TestResources) handler.Request {
req.Decrypt = ptr.P(false)
return req
},
ExpectedStatus: 403,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align test intent: set Decrypt=true to match the test name.

Name says "decrypt permission without read permission" but request sets Decrypt=false. Suggest setting it to true to actually exercise decrypt path while lacking read.

-					ModifyRequest: func(req handler.Request, res authz.TestResources) handler.Request {
-						req.Decrypt = ptr.P(false)
+					ModifyRequest: func(req handler.Request, res authz.TestResources) handler.Request {
+						req.Decrypt = ptr.P(true)
 						return req
 					},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Name: "decrypt permission without read permission",
Permissions: []string{"api.*.decrypt_key"}, // Has decrypt but not read
ModifyRequest: func(req handler.Request, res authz.TestResources) handler.Request {
req.Decrypt = ptr.P(false)
return req
},
ExpectedStatus: 403,
Name: "decrypt permission without read permission",
Permissions: []string{"api.*.decrypt_key"}, // Has decrypt but not read
ModifyRequest: func(req handler.Request, res authz.TestResources) handler.Request {
req.Decrypt = ptr.P(true)
return req
},
ExpectedStatus: 403,
🤖 Prompt for AI Agents
In go/apps/api/routes/v2_keys_get_key/403_test.go around lines 61 to 67, the
test named "decrypt permission without read permission" incorrectly sets
req.Decrypt to false; change the test's ModifyRequest to set Decrypt to true so
the request actually exercises the decrypt path while the permissions omit read,
and keep ExpectedStatus 403 unchanged to validate access denial.

Comment on lines +14 to +58
authz.Test403(t,
authz.PermissionTestConfig[handler.Request, handler.Response]{
SetupHandler: func(h *testutil.Harness) zen.Route {
return &handler.Handler{
DB: h.DB,
Keys: h.Keys,
Logger: h.Logger,
Auditlogs: h.Auditlogs,
KeyCache: h.Caches.VerificationKeyByHash,
}
},
RequiredPermissions: []string{"api.*.update_key", "rbac.*.add_role_to_key", "rbac.*.remove_role_from_key"},
SetupResources: func(h *testutil.Harness) authz.TestResources {
api := h.CreateApi(seed.CreateApiRequest{
WorkspaceID: h.Resources().UserWorkspace.ID,
})

roleID := h.CreateRole(seed.CreateRoleRequest{
WorkspaceID: h.Resources().UserWorkspace.ID,
Name: "test-role",
})

keyResp := h.CreateKey(seed.CreateKeyRequest{
WorkspaceID: h.Resources().UserWorkspace.ID,
KeyAuthID: api.KeyAuthID.String,
})

return authz.TestResources{
WorkspaceID: h.Resources().UserWorkspace.ID,
ApiID: api.ID,
KeyAuthID: api.KeyAuthID.String,
KeyID: keyResp.KeyID,
Custom: map[string]string{
"role_id": roleID,
},
}
},
CreateRequest: func(res authz.TestResources) handler.Request {
return handler.Request{
KeyId: res.KeyID,
Roles: []string{res.Custom["role_id"]},
}
},
},
})
keyID := keyResponse.KeyID
roleID := keyResponse.RolesIds[0]

// Test case for insufficient permissions - missing update_key
t.Run("missing update_key permission", func(t *testing.T) {
// Create a root key with some permissions but not update_key
rootKey := h.CreateRootKey(workspace.ID, "api.*.create_key") // Only has create, not update

headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
}

req := handler.Request{
KeyId: keyID,
Roles: []string{roleID},
}

res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](
h,
route,
headers,
req,
)

require.Equal(t, 403, res.Status)
require.NotNil(t, res.Body)
require.NotNil(t, res.Body.Error)
require.Contains(t, res.Body.Error.Detail, "permission")
})

// Test case for no permissions at all
t.Run("no permissions", func(t *testing.T) {
// Create a root key with no permissions
rootKey := h.CreateRootKey(workspace.ID)

headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
}

req := handler.Request{
KeyId: keyID,
Roles: []string{roleID},
}

res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](
h,
route,
headers,
req,
)

require.Equal(t, 403, res.Status)
require.NotNil(t, res.Body)
require.NotNil(t, res.Body.Error)
require.Contains(t, res.Body.Error.Detail, "permission")
})

)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Permissions and seeding look correct; verify slugs and subset coverage.

Config matches the endpoint intent. Please verify the route enforces these exact slugs and that Test403 generates insufficient-subset cases for multi-permission endpoints.

Consider adding explicit cases for “only add” and “only remove” if the helper doesn’t already cover subsets.


🏁 Script executed:

#!/bin/bash
# Confirm enforced permissions in handler
rg -nP '(add_role_to_key|remove_role_from_key|update_key)' go/apps/api/routes/v2_keys_set_roles -C3

# Sanity-check helper generates subset tests (look for combos/subsets)
rg -n 'subset|combination|insufficient' go/pkg/testutil/authz

Length of output: 2865



---

🏁 Script executed:

```shell
#!/bin/bash
# Find and inspect handler implementation
fd -t f 'handler\.go|handler\.Handler' go/apps/api/routes/v2_keys_set_roles

# Look for authorization checks in the handler
rg -nP 'CheckPermission|Authz|enforce' go/apps/api/routes/v2_keys_set_roles/handler.go -A5 -B2

Length of output: 211



---

🏁 Script executed:

```shell
#!/bin/bash
# List directory to see what files exist
ls -la go/apps/api/routes/v2_keys_set_roles/ 2>/dev/null || echo "Directory listing failed"

# Search for Handler struct definition in the route's package
rg -n 'type Handler struct' go/apps/api/routes/v2_keys_set_roles/ -A20

# Search more broadly for authorization checks near add_role_to_key
rg -n 'add_role_to_key.*CheckPermission|CheckPermission.*add_role_to_key' --type=go -C5

Length of output: 2182



---

🏁 Script executed:

```shell
#!/bin/bash
# View the Handle method to see authorization/permission checks
cat go/apps/api/routes/v2_keys_set_roles/handler.go | tail -n +47 | head -n 100

Length of output: 2818



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for add_role_to_key and remove_role_from_key checks in handler
rg -n 'AddRoleToKey|RemoveRoleFromKey|add_role_to_key|remove_role_from_key' go/apps/api/routes/v2_keys_set_roles/handler.go

# Also check what rbac constants exist
rg -n 'rbac\.(Add|Remove|Update)' go/apps/api/routes/v2_keys_set_roles/handler.go

Length of output: 71



---

🏁 Script executed:

```shell
#!/bin/bash
# Get full handler.go line count and verify no other VerifyRootKey calls
wc -l go/apps/api/routes/v2_keys_set_roles/handler.go

# Search for all VerifyRootKey calls to confirm only one permission check
rg -n 'VerifyRootKey' go/apps/api/routes/v2_keys_set_roles/handler.go -A2

Length of output: 169


Fix RequiredPermissions to match handler enforcement.

The handler only enforces api.*.update_key. The add_role_to_key and remove_role_from_key permissions are never checked in the VerifyRootKey call. Update line 25 to reflect the actual enforced permission:

RequiredPermissions: []string{"api.*.update_key"},
🤖 Prompt for AI Agents
In go/apps/api/routes/v2_keys_set_roles/403_test.go around lines 14 to 58, the
test's RequiredPermissions includes "rbac.*.add_role_to_key" and
"rbac.*.remove_role_from_key" but the handler only enforces "api.*.update_key";
update the RequiredPermissions entry to only include "api.*.update_key" so the
test matches the handler's actual permission checks.

Comment on lines +40 to +45
func Test401[TReq any, TRes any](
t *testing.T,
setupHandler func(*testutil.Harness) zen.Route,
createRequest func() TReq,
) {
t.Helper()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Make expected 401/400 codes configurable.

Endpoints differ on whether missing/malformed Authorization yields 400 or 401. Add optional options to override defaults per route to reduce flakiness.

-func Test401[TReq any, TRes any](
-	t *testing.T,
-	setupHandler func(*testutil.Harness) zen.Route,
-	createRequest func() TReq,
-) {
+type Auth401Option func(*auth401Config)
+type auth401Config struct {
+	MissingAuthStatus   int
+	MalformedAuthStatus int
+}
+func WithMissingAuthStatus(status int) Auth401Option   { return func(c *auth401Config) { c.MissingAuthStatus = status } }
+func WithMalformedAuthStatus(status int) Auth401Option { return func(c *auth401Config) { c.MalformedAuthStatus = status } }
+
+func Test401[TReq any, TRes any](
+	t *testing.T,
+	setupHandler func(*testutil.Harness) zen.Route,
+	createRequest func() TReq,
+	opts ...Auth401Option,
+) {
 	t.Helper()
+	cfg := &auth401Config{
+		MissingAuthStatus:   http.StatusBadRequest,
+		MalformedAuthStatus: http.StatusBadRequest,
+	}
+	for _, opt := range opts {
+		opt(cfg)
+	}
 
 	testCases := []struct {
 		name           string
 		authorization  string
 		expectedStatus int
 	}{
 		{
 			name:           "invalid bearer token",
 			authorization:  "Bearer invalid_token_12345",
 			expectedStatus: http.StatusUnauthorized,
 		},
 		{
 			name:           "nonexistent key",
 			authorization:  fmt.Sprintf("Bearer %s", uid.New(uid.KeyPrefix)),
 			expectedStatus: http.StatusUnauthorized,
 		},
 		{
 			name:           "bearer with extra spaces",
 			authorization:  "Bearer   invalid_key_with_spaces   ",
 			expectedStatus: http.StatusUnauthorized,
 		},
 		{
 			name:           "missing authorization header",
 			authorization:  "",
-			expectedStatus: http.StatusBadRequest, // Missing auth typically returns 400
+			expectedStatus: cfg.MissingAuthStatus,
 		},
 		{
 			name:           "empty authorization header",
 			authorization:  " ",
-			expectedStatus: http.StatusBadRequest,
+			expectedStatus: cfg.MissingAuthStatus,
 		},
 		{
 			name:           "malformed authorization header - no Bearer prefix",
 			authorization:  "invalid_token_without_bearer",
-			expectedStatus: http.StatusBadRequest,
+			expectedStatus: cfg.MalformedAuthStatus,
 		},
 		{
 			name:           "malformed authorization header - Bearer only",
 			authorization:  "Bearer",
-			expectedStatus: http.StatusBadRequest,
+			expectedStatus: cfg.MalformedAuthStatus,
 		},
 	}

Also applies to: 47-87

🤖 Prompt for AI Agents
In go/pkg/testutil/authz/authz.go around lines 40-45 (and also apply the same
change for 47-87), the Test401 helper hardcodes expected HTTP status for
missing/malformed Authorization; add an options parameter (e.g., a small struct
with fields like ExpectedMissingCode and ExpectedMalformedCode or a single
ExpectedCode) with sensible defaults (401) so callers can override per-route
behavior, update the function signature to accept the options (or a variadic
opts ...Option) and use the provided values when asserting responses, and
propagate the same configurable option pattern to the other helper(s) in lines
47-87 so tests can set 400 or 401 as needed without flakiness.

Comment on lines +123 to +130
// The test suite automatically generates test cases for:
// - No permissions
// - Wrong permissions (different action)
// - Unrelated permissions
// - Resource-specific permissions (if applicable)
// - Cross-workspace scenarios
// - Permission combinations
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Doc/code mismatch: “Cross‑workspace scenarios” aren’t implemented.

The suite doesn’t generate a cross‑workspace case. Either drop this bullet or add a test that uses a root key from another workspace and expects 403.

Comment on lines +304 to +312
if tc.shouldPass {
require.NotEqual(t, http.StatusForbidden, res.Status,
"expected success, got %d, body: %s", res.Status, res.RawBody)
require.NotEqual(t, http.StatusUnauthorized, res.Status,
"expected success, got %d, body: %s", res.Status, res.RawBody)
} else {
require.Equal(t, http.StatusForbidden, res.Status,
"expected 403, got %d, body: %s", res.Status, res.RawBody)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Tighten “pass” assertions to catch 5xx regressions.

A 5xx currently passes. Assert non-error status (<400) for shouldPass cases.

-			if tc.shouldPass {
-				require.NotEqual(t, http.StatusForbidden, res.Status,
-					"expected success, got %d, body: %s", res.Status, res.RawBody)
-				require.NotEqual(t, http.StatusUnauthorized, res.Status,
-					"expected success, got %d, body: %s", res.Status, res.RawBody)
+			if tc.shouldPass {
+				require.Less(t, res.Status, 400,
+					"expected non-error status, got %d, body: %s", res.Status, res.RawBody)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if tc.shouldPass {
require.NotEqual(t, http.StatusForbidden, res.Status,
"expected success, got %d, body: %s", res.Status, res.RawBody)
require.NotEqual(t, http.StatusUnauthorized, res.Status,
"expected success, got %d, body: %s", res.Status, res.RawBody)
} else {
require.Equal(t, http.StatusForbidden, res.Status,
"expected 403, got %d, body: %s", res.Status, res.RawBody)
}
if tc.shouldPass {
require.Less(t, res.Status, 400,
"expected non-error status, got %d, body: %s", res.Status, res.RawBody)
} else {
require.Equal(t, http.StatusForbidden, res.Status,
"expected 403, got %d, body: %s", res.Status, res.RawBody)
}
🤖 Prompt for AI Agents
In go/pkg/testutil/authz/authz.go around lines 304 to 312, the test currently
only checks that successful cases are not 401 or 403 which lets 5xx responses
slip through; change the assertions for tc.shouldPass to require that res.Status
is less than 400 (non-error) and keep the existing formatted failure message
including res.Status and res.RawBody so any 5xx will fail the test; retain the
else branch asserting 403 for shouldPass==false.

Comment on lines +136 to +147
AdditionalPermissionTests: []authz.PermissionTestCase[handler.Request]{
{
Name: "special scenario",
Permissions: []string{"some.permission"},
ModifyRequest: func(req handler.Request) handler.Request {
req.SomeField = "modified"
return req
},
ExpectedStatus: 403,
},
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

README/API mismatch: ModifyRequest signature differs from usage.

Examples show ModifyRequest(func(req TReq) TReq) but tests use ModifyRequest(func(req TReq, res TestResources) TReq). Update examples and the PermissionTestCase docs to the two-arg form to avoid confusion.

-            AdditionalPermissionTests: []authz.PermissionTestCase[handler.Request]{
+            AdditionalPermissionTests: []authz.PermissionTestCase[handler.Request]{
                 {
                     Name:        "special scenario",
                     Permissions: []string{"some.permission"},
-                    ModifyRequest: func(req handler.Request) handler.Request {
+                    ModifyRequest: func(req handler.Request, res authz.TestResources) handler.Request {
                         req.SomeField = "modified"
                         return req
                     },
                     ExpectedStatus: 403,
                 },
             },

Also clarify in “PermissionTestCase” that ModifyRequest has signature:

ModifyRequest func(req TReq, res TestResources) TReq

Also applies to: 179-186

🤖 Prompt for AI Agents
In go/pkg/testutil/authz/README.md around lines 136-147 (and also update lines
179-186), the example and docs show ModifyRequest as func(req TReq) TReq but the
tests use the two-argument form; change the examples and the PermissionTestCase
documentation to show ModifyRequest as func(req TReq, res TestResources) TReq,
and explicitly state in the PermissionTestCase field description that
ModifyRequest has signature: ModifyRequest func(req TReq, res TestResources)
TReq so examples and docs match actual test usage.

Copy link
Collaborator

@chronark chronark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left some comments

@Flo4604 Flo4604 closed this Dec 12, 2025
@Flo4604 Flo4604 deleted the refactor/40X_tests branch January 22, 2026 14:05
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.

Cleanup 403/401 Tests

2 participants