Skip to content

chore: Migrate Toggle Flag - BED-8586#2928

Open
Flake85 wants to merge 8 commits into
mainfrom
BED-8586
Open

chore: Migrate Toggle Flag - BED-8586#2928
Flake85 wants to merge 8 commits into
mainfrom
BED-8586

Conversation

@Flake85

@Flake85 Flake85 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

We want to move ToggleFlag to the new API architecture. The handler should be moved out of v2 into the server folder following the new pattern.

Motivation and Context

Resolves BED-8586

Why is this change required? What problem does it solve?
We are migrating endpoints to use the new architecture.

How Has This Been Tested?

Please describe in detail how you tested your changes.
Include details of your testing environment, and the tests you ran to
see how your change affects other areas of the code, etc.

i used bruno to test the endpoints and ran all of the tests that were added/modified.

Screenshots (optional):

Types of changes

  • Chore (a change that does not modify the application functionality)

Checklist:

Summary by CodeRabbit

  • New Features
    • Added application configuration endpoints: GET /api/v2/config and PUT /api/v2/config.
    • Feature flags are now served by the feature module, supporting GET /api/v2/features and PUT /api/v2/features/{feature_id}/toggle.
  • Bug Fixes
    • Improved feature-flag toggle responses: invalid feature IDs return 400, missing flags return 404, and non-user-updatable flags return 403.
  • Tests
    • Expanded unit, integration, and end-to-end coverage for feature-flag routing, handlers, services, and database behavior.

@Flake85 Flake85 self-assigned this Jun 25, 2026
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Feature flags now use module-backed list/toggle handlers, store updates, and route registration. The old v2 feature endpoints were removed, and tests now cover persistence, HTTP behavior, and module wiring.

Changes

Feature Flags API

Layer / File(s) Summary
Service contract
server/featureflags/internal/services/*
The service package adds list/toggle methods, a new domain error, audit-data generation, and expanded database ports, with mocks and tests updated for the new contract.
AppDB store
server/featureflags/internal/appdb/*
The store adds shared key and ID lookup, list and transactional update operations, and audit-log writes, with unit and integration tests covering lookup, listing, auditing, and rollback behavior.
HTTP handlers
server/featureflags/internal/handlers/*, server/featureflags/featureflags_e2e_test.go
Feature-flag views and handlers add JSON response mapping, toggle parsing, service error translation, and unit/integration tests for list, toggle, and enabled checks.
Module wiring
cmd/api/src/api/registration/v2.go, server/featureflags/featureflags.go, server/featureflags/internal/routes/*, server/modules/*, server/featureflags/featureflags_test.go, server/featureflags/mocks/*
The featureflags module now builds an adapter, registers GET/PUT routes, removes the old v2 feature endpoints, and adds route and module tests.

Sequence Diagram(s)

sequenceDiagram
  participant Router
  participant Handlers
  participant Service
  participant Store

  Router->>Handlers: GET /api/v2/features
  Handlers->>Service: GetAllFlags(ctx)
  Service->>Store: GetAllFlags(ctx)
  Store-->>Service: []FeatureFlag
  Service-->>Handlers: []FeatureFlag
  Handlers-->>Router: JSON response

  Router->>Handlers: PUT /api/v2/features/{feature_id}/toggle
  Handlers->>Service: ToggleFlag(ctx, id, requestedBy)
  Service->>Store: GetFlagByID(ctx, id)
  Store-->>Service: FeatureFlag
  Service->>Store: SetFlag(ctx, featureFlag)
  Store-->>Service: updated FeatureFlag
  Service-->>Handlers: updated FeatureFlag
  Handlers-->>Router: JSON response / HTTP status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • SpecterOps/BloodHound#2922: Updates the removed ToggleFlag flow in cmd/api/src/api/v2/flag.go, which overlaps directly with the endpoint replacement in this PR.

Suggested reviewers

  • superlinkx
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and correctly reflects the main migration of ToggleFlag to the new API architecture.
Description check ✅ Passed The description follows the required template and includes the change summary, motivation, testing notes, issue reference, and checklist.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch BED-8586

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.

@coderabbitai coderabbitai Bot added api A pull request containing changes affecting the API code. go Pull requests that update go code infrastructure A pull request containing changes affecting the infrastructure code. labels Jun 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (6)
server/featureflags/internal/services/services.go (1)

110-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

requestedBy is accepted but never used.

ToggleFlag takes requestedBy yet ignores it — the audit actor is resolved from the context inside SetFlag. The handler computes user.ID.String() and passes it here expecting it to be recorded, but it is silently discarded, so the value the caller supplies and the actor actually persisted can diverge. Either drop the parameter or thread it through to the audit write.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/featureflags/internal/services/services.go` around lines 110 - 127,
ToggleFlag currently accepts requestedBy but never uses it, so the
caller-supplied audit actor is discarded. Update Service.ToggleFlag and its
SetFlag call path to either remove requestedBy entirely or pass it through so
the audit write records that exact value instead of resolving a different actor
from context; use ToggleFlag and SetFlag as the key symbols when making the
change.
server/featureflags/internal/appdb/appdb.go (1)

138-144: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant pgx.ErrNoRows branch after Query.

querier.Query doesn't surface pgx.ErrNoRows for an empty result set; that case is correctly handled by the CollectOneRow branch below (Line 147). This first check at Line 139-141 is unreachable for the no-rows case and can be dropped, leaving only the generic error check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/featureflags/internal/appdb/appdb.go` around lines 138 - 144, Remove
the unreachable pgx.ErrNoRows handling after querier.Query in appdb.go, since
Query does not return that error for empty results and the no-rows case is
already handled later by CollectOneRow. Keep the generic err check in the Query
path and leave the FeatureFlag lookup logic unchanged otherwise.
server/featureflags/internal/routes/routes.go (1)

24-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Parameter name shadows the imported handlers package.

handlers *handlers.Handlers shadows the handlers import within the function body. It works here but is a readability/footgun risk if the package is later referenced inside Register. Consider handlerSet *handlers.Handlers (consistent with the rest of the cohort).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/featureflags/internal/routes/routes.go` at line 24, The Register
function’s parameter name shadows the imported handlers package, which can
become confusing or break future references inside the function body. Rename the
parameter in Register from handlers to a clearer name like handlerSet, and
update any uses within the function to match while keeping the
*handlers.Handlers type unchanged.
server/featureflags/featureflags.go (1)

45-62: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated store/service/handler wiring.

NewFeatureFlagRequestAdapter and Register build the identical store → svc → handlerSet chain. Consider extracting a small private constructor (e.g. newHandlers(pool)) so both entry points share one source of truth.

♻️ Proposed refactor
+func newHandlers(pool *pgxpool.Pool) *handlers.Handlers {
+	var (
+		store = appdb.NewStore(pool)
+		svc   = services.NewService(store)
+	)
+	return handlers.NewHandlersContainer(svc)
+}
+
 func NewFeatureFlagRequestAdapter(pool *pgxpool.Pool) FeatureFlagRequestAdapter {
-	var (
-		store      = appdb.NewStore(pool)
-		svc        = services.NewService(store)
-		handlerSet = handlers.NewHandlersContainer(svc)
-	)
-
-	return handlerSet
+	return newHandlers(pool)
 }

 func Register(routerInst *router.Router, pool *pgxpool.Pool) {
-	var (
-		store      = appdb.NewStore(pool)
-		svc        = services.NewService(store)
-		handlerSet = handlers.NewHandlersContainer(svc)
-	)
-
-	routes.Register(routerInst, handlerSet)
+	routes.Register(routerInst, newHandlers(pool))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/featureflags/featureflags.go` around lines 45 - 62, Both
NewFeatureFlagRequestAdapter and Register duplicate the same appdb.NewStore ->
services.NewService -> handlers.NewHandlersContainer wiring; extract that shared
setup into a small private helper (for example, a newHandlers function) and have
both entry points call it so the featureflags package has a single source of
truth for constructing the handler set.
server/featureflags/internal/handlers/handlers_test.go (1)

213-220: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the unauthenticated case on the real handler contract.

Line 216 builds a request with no bhctx.Context, but Handlers.ToggleFlag reads bhctx.FromRequest(request).AuthCtx. Attaching an empty bhctx.Context here would exercise the intended “no user on auth context” branch instead of depending on missing-context fallback behavior.

Suggested test setup
 			} else {
 				req, err := http.NewRequest(http.MethodPut, "/api/v2/features/"+tt.featureID+"/toggle", nil)
 				require.NoError(t, err)
-				request = req
+				request = bhctx.SetRequestContext(req, &bhctx.Context{})
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/featureflags/internal/handlers/handlers_test.go` around lines 213 -
220, The unauthenticated test setup in handlers_test.go should use the real
handler contract by providing an empty bhctx.Context instead of a request with
no bhctx.Context at all. Update the test case around the request creation for
ToggleFlag so that it still calls Handlers.ToggleFlag through
bhctx.FromRequest(request).AuthCtx, but exercises the “no user on auth context”
path explicitly rather than relying on fallback behavior from a missing context.
server/featureflags/featureflags_e2e_test.go (1)

247-257: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert the toggle was persisted, not just returned.

This test only checks the response body. Because Service.ToggleFlag flips the struct before calling SetFlag, it would still pass if the write path regressed after the in-memory update. Add a follow-up read against feature_flags to verify the committed enabled state.

Suggested assertion
 		var envelope featureFlagEnvelope
 		require.NoError(t, json.NewDecoder(resp.Body).Decode(&envelope))
 		assert.Equal(t, featureID, envelope.Data.ID)
 		assert.True(t, envelope.Data.Enabled, "Enabled should have flipped from false to true")
+
+		var persistedEnabled bool
+		err = db.Pool().QueryRow(ctx, `SELECT enabled FROM feature_flags WHERE id = $1`, featureID).Scan(&persistedEnabled)
+		require.NoError(t, err)
+		assert.True(t, persistedEnabled, "toggled value should be persisted")
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/featureflags/featureflags_e2e_test.go` around lines 247 - 257, The
toggle test currently only validates the HTTP response from newToggleRequest and
featureFlagEnvelope, so it can miss a broken persistence write after
Service.ToggleFlag updates the in-memory struct. After asserting the response,
add a follow-up read against feature_flags to verify the committed enabled state
for featureID, and assert that the stored value matches the toggled result
rather than relying only on the returned envelope.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/featureflags/featureflags_e2e_test.go`:
- Around line 104-106: The parsing in the feature flag E2E setup is incomplete
because `strings.FieldsSeq` is iterated but the split key/value pairs are not
being stored; update the loop in the `environmentMap` construction so the
`strings.SplitN` result (`parts`) is assigned into the map when the pair is
valid. Use the `cfg.Database.Connection` parsing block in the test helper to
ensure each extracted entry is mapped from `parts[0]` to `parts[1]`.

In `@server/featureflags/internal/appdb/appdb.go`:
- Around line 266-273: The UPDATE path in appdb.UpdateFeatureFlag is checking
pgx.ErrNoRows after tx.Exec, but Exec won’t return that for zero-row updates.
After calling tx.Exec and building the query in updateBuilder, inspect the
command tag’s RowsAffected value and return services.ErrNotFound when it is 0;
keep returning any real exec error unchanged. Remove the dead pgx.ErrNoRows
branch and base the not-found behavior on the Exec result instead.

In `@server/featureflags/internal/handlers/views.go`:
- Around line 26-49: The FeatureFlagView response includes a leaky DeletedAt
field that serializes incorrectly because sql.NullTime is not JSON-friendly and
BuildFeatureFlagView never populates it. Remove DeletedAt from FeatureFlagView
and from any related JSON contract, then update BuildFeatureFlagView to return
only the populated fields from services.FeatureFlag; if a deleted timestamp is
truly needed later, switch to a properly marshaled time type such as *time.Time
with omitempty. Also drop the now-unused database/sql import after removing the
field.

---

Nitpick comments:
In `@server/featureflags/featureflags_e2e_test.go`:
- Around line 247-257: The toggle test currently only validates the HTTP
response from newToggleRequest and featureFlagEnvelope, so it can miss a broken
persistence write after Service.ToggleFlag updates the in-memory struct. After
asserting the response, add a follow-up read against feature_flags to verify the
committed enabled state for featureID, and assert that the stored value matches
the toggled result rather than relying only on the returned envelope.

In `@server/featureflags/featureflags.go`:
- Around line 45-62: Both NewFeatureFlagRequestAdapter and Register duplicate
the same appdb.NewStore -> services.NewService -> handlers.NewHandlersContainer
wiring; extract that shared setup into a small private helper (for example, a
newHandlers function) and have both entry points call it so the featureflags
package has a single source of truth for constructing the handler set.

In `@server/featureflags/internal/appdb/appdb.go`:
- Around line 138-144: Remove the unreachable pgx.ErrNoRows handling after
querier.Query in appdb.go, since Query does not return that error for empty
results and the no-rows case is already handled later by CollectOneRow. Keep the
generic err check in the Query path and leave the FeatureFlag lookup logic
unchanged otherwise.

In `@server/featureflags/internal/handlers/handlers_test.go`:
- Around line 213-220: The unauthenticated test setup in handlers_test.go should
use the real handler contract by providing an empty bhctx.Context instead of a
request with no bhctx.Context at all. Update the test case around the request
creation for ToggleFlag so that it still calls Handlers.ToggleFlag through
bhctx.FromRequest(request).AuthCtx, but exercises the “no user on auth context”
path explicitly rather than relying on fallback behavior from a missing context.

In `@server/featureflags/internal/routes/routes.go`:
- Line 24: The Register function’s parameter name shadows the imported handlers
package, which can become confusing or break future references inside the
function body. Rename the parameter in Register from handlers to a clearer name
like handlerSet, and update any uses within the function to match while keeping
the *handlers.Handlers type unchanged.

In `@server/featureflags/internal/services/services.go`:
- Around line 110-127: ToggleFlag currently accepts requestedBy but never uses
it, so the caller-supplied audit actor is discarded. Update Service.ToggleFlag
and its SetFlag call path to either remove requestedBy entirely or pass it
through so the audit write records that exact value instead of resolving a
different actor from context; use ToggleFlag and SetFlag as the key symbols when
making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7d4eb8e4-f741-400d-b7f4-357685694f9f

📥 Commits

Reviewing files that changed from the base of the PR and between 113c23e and 764c4d9.

📒 Files selected for processing (21)
  • cmd/api/src/api/registration/v2.go
  • cmd/api/src/api/v2/flag.go
  • cmd/api/src/api/v2/flag_test.go
  • server/featureflags/featureflags.go
  • server/featureflags/featureflags_e2e_test.go
  • server/featureflags/featureflags_test.go
  • server/featureflags/internal/appdb/appdb.go
  • server/featureflags/internal/appdb/appdb_integration_test.go
  • server/featureflags/internal/appdb/appdb_test.go
  • server/featureflags/internal/handlers/handlers.go
  • server/featureflags/internal/handlers/handlers_test.go
  • server/featureflags/internal/handlers/mocks/featureflag.go
  • server/featureflags/internal/handlers/views.go
  • server/featureflags/internal/routes/routes.go
  • server/featureflags/internal/routes/routes_test.go
  • server/featureflags/internal/services/mocks/database.go
  • server/featureflags/internal/services/services.go
  • server/featureflags/internal/services/services_test.go
  • server/featureflags/mocks/featureflagrequestadapter.go
  • server/modules/modules.go
  • server/modules/modules_test.go
💤 Files with no reviewable changes (3)
  • cmd/api/src/api/registration/v2.go
  • cmd/api/src/api/v2/flag_test.go
  • cmd/api/src/api/v2/flag.go

Comment thread server/featureflags/featureflags_e2e_test.go
Comment on lines +266 to +273
sqlQuery, args := updateBuilder.Build()
_, err = tx.Exec(ctx, sqlQuery, args...)
if errors.Is(err, pgx.ErrNoRows) {
return services.ErrNotFound
}
if err != nil {
return err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Does pgx v5 Conn/Tx Exec return pgx.ErrNoRows when an UPDATE statement affects zero rows, or does it return a CommandTag with RowsAffected of 0?

💡 Result:

In pgx v5, the Exec method does not return pgx.ErrNoRows when an UPDATE statement affects zero rows [1]. Instead, it returns a pgconn.CommandTag with a RowsAffected count of 0, which is considered a successful execution [1]. The pgx.ErrNoRows error is specific to QueryRow operations (and its variants like CollectOneRow) when they are expected to return exactly one row but find none [2][3]. Since an UPDATE statement is a command rather than a query that returns a result set, it does not trigger the "no rows found" error condition [3]. To check how many rows were updated, you should inspect the CommandTag returned by Exec: result, err:= conn.Exec(ctx, "UPDATE my_table SET val = $1 WHERE id = $2", val, id) if err!= nil { // handle error } rowsAffected:= result.RowsAffected if rowsAffected == 0 { // handle the case where no rows were updated }

Citations:


Replace pgx.ErrNoRows check with RowsAffected check for UPDATE operations

pgx.ErrNoRows is only returned by QueryRow operations when zero rows are found, not by Exec. An UPDATE affecting zero rows returns nil error with RowsAffected() == 0. The current check is dead code; inspect the command tag to return services.ErrNotFound when no rows are updated.

Proposed fix
-	sqlQuery, args := updateBuilder.Build()
-	_, err = tx.Exec(ctx, sqlQuery, args...)
-	if errors.Is(err, pgx.ErrNoRows) {
-		return services.ErrNotFound
-	}
-	if err != nil {
-		return err
-	}
+	sqlQuery, args := updateBuilder.Build()
+	commandTag, err := tx.Exec(ctx, sqlQuery, args...)
+	if err != nil {
+		return err
+	}
+	if commandTag.RowsAffected() == 0 {
+		return services.ErrNotFound
+	}
📝 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
sqlQuery, args := updateBuilder.Build()
_, err = tx.Exec(ctx, sqlQuery, args...)
if errors.Is(err, pgx.ErrNoRows) {
return services.ErrNotFound
}
if err != nil {
return err
}
sqlQuery, args := updateBuilder.Build()
commandTag, err := tx.Exec(ctx, sqlQuery, args...)
if err != nil {
return err
}
if commandTag.RowsAffected() == 0 {
return services.ErrNotFound
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/featureflags/internal/appdb/appdb.go` around lines 266 - 273, The
UPDATE path in appdb.UpdateFeatureFlag is checking pgx.ErrNoRows after tx.Exec,
but Exec won’t return that for zero-row updates. After calling tx.Exec and
building the query in updateBuilder, inspect the command tag’s RowsAffected
value and return services.ErrNotFound when it is 0; keep returning any real exec
error unchanged. Remove the dead pgx.ErrNoRows branch and base the not-found
behavior on the Exec result instead.

Comment thread server/featureflags/internal/handlers/views.go

@stephanieslamb stephanieslamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good! I still want to do a quick peek through test cases. But I think with these changes, it should be good.

// feature_id path parameter. The handler delegates to the service layer, which
// loads the existing flag, validates that it is user-updatable, flips its
// Enabled value, persists the result, and conditionally triggers an analysis
// request when the ADCS flag is being disabled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should probably update this comment since we removed the ADCS check and this no longer triggers analysis

Comment thread server/featureflags/featureflags.go Outdated
type Service interface {
type FeatureFlagRequestAdapter interface {
IsEnabled(ctx context.Context, key string) (bool, error)
ToggleFlag(response http.ResponseWriter, request *http.Request)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure this is needed in this interface? Is ToggleFlag called cross slice?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i went ahead and just removed it. on the off chance that it is being used elsewhere, we can always add it back when/if another slice uses it.

IsEnabled(ctx context.Context, key string) (bool, error)
}

// Handlers is a dependency injection container for app_config handlers

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Handlers is a dependency injection container for featureflags handlers

// handleAppConfigError maps service-layer errors to HTTP responses, translating
// known sentinel errors to their corresponding status codes and falling back to
// a logged 500 for anything unexpected.
func handleAppConfigError(request *http.Request, response http.ResponseWriter, err error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

handleAppConfigError -> handleFeatureFlagError

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/modules/modules_test.go`:
- Around line 40-48: The panic tests in modules_test.go only verify that
modules.Register panics, so they can pass on unrelated failures; update
TestRegister_PanicsOnNilRouter and the other nil-guard tests to assert the exact
panic value with assert.PanicsWithValue. Use the concrete nil-check messages
from modules.Register as the expected values, so the contract is pinned to the
intended guard rather than any panic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d66a1c87-0b32-4a22-b0f2-e2e2f9736e4f

📥 Commits

Reviewing files that changed from the base of the PR and between 80da836 and 0db5647.

📒 Files selected for processing (3)
  • cmd/api/src/api/registration/v2.go
  • server/modules/modules.go
  • server/modules/modules_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/modules/modules.go
  • cmd/api/src/api/registration/v2.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/modules/modules_test.go`:
- Around line 40-48: The panic tests in modules_test.go only verify that
modules.Register panics, so they can pass on unrelated failures; update
TestRegister_PanicsOnNilRouter and the other nil-guard tests to assert the exact
panic value with assert.PanicsWithValue. Use the concrete nil-check messages
from modules.Register as the expected values, so the contract is pinned to the
intended guard rather than any panic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d66a1c87-0b32-4a22-b0f2-e2e2f9736e4f

📥 Commits

Reviewing files that changed from the base of the PR and between 80da836 and 0db5647.

📒 Files selected for processing (3)
  • cmd/api/src/api/registration/v2.go
  • server/modules/modules.go
  • server/modules/modules_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/modules/modules.go
  • cmd/api/src/api/registration/v2.go
🛑 Comments failed to post (1)
server/modules/modules_test.go (1)

40-48: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the expected panic value.

These tests currently pass on any panic, so a downstream failure in module registration would still look like the intended nil-guard fired. Using assert.PanicsWithValue here makes the test pin the contract in modules.Register.

Proposed fix
-	assert.Panics(t, func() {
+	assert.PanicsWithValue(t, "modules: Register requires a non-nil Router", func() {
 		modules.Register(modules.Deps{
 			Router:              nil,
 			Pool:                new(pgxpool.Pool),
 			Graph:               &graph.DatabaseSwitch{},
 			RateLimitMiddleware: noopRateLimit,
 		})
 	})
-	assert.Panics(t, func() {
+	assert.PanicsWithValue(t, "modules: Register requires a non-nil Pool", func() {
 		modules.Register(modules.Deps{
 			Router:              &routerInst,
 			Pool:                nil,
 			Graph:               &graph.DatabaseSwitch{},
 			RateLimitMiddleware: noopRateLimit,
 		})
 	})
-	assert.Panics(t, func() {
+	assert.PanicsWithValue(t, "modules: Register requires a non-nil Graph", func() {
 		modules.Register(modules.Deps{
 			Router:              &routerInst,
 			Pool:                new(pgxpool.Pool),
 			Graph:               nil,
 			RateLimitMiddleware: noopRateLimit,
 		})
 	})
-	assert.Panics(t, func() {
+	assert.PanicsWithValue(t, "modules: Register requires a non-nil RateLimitMiddleware", func() {
 		modules.Register(modules.Deps{
 			Router:              &routerInst,
 			Pool:                new(pgxpool.Pool),
 			Graph:               &graph.DatabaseSwitch{},
 			RateLimitMiddleware: nil,
 		})
 	})

Also applies to: 58-65, 75-82, 92-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/modules_test.go` around lines 40 - 48, The panic tests in
modules_test.go only verify that modules.Register panics, so they can pass on
unrelated failures; update TestRegister_PanicsOnNilRouter and the other
nil-guard tests to assert the exact panic value with assert.PanicsWithValue. Use
the concrete nil-check messages from modules.Register as the expected values, so
the contract is pinned to the intended guard rather than any panic.

}

// ToggleFlag enables/disables the feature flag by the feature flag id
func (s *Service) ToggleFlag(ctx context.Context, id int32, requestedBy string) (FeatureFlag, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think requestedBy is being used. I think this is currently captured in the insertAuditLog function.

ID int32 `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt time.Time `json:"deleted_at"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

DeletedAt can be removed

@coderabbitai coderabbitai Bot removed api A pull request containing changes affecting the API code. infrastructure A pull request containing changes affecting the infrastructure code. go Pull requests that update go code labels Jun 30, 2026
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.

2 participants