-
-
Notifications
You must be signed in to change notification settings - Fork 278
feat: update permify api v1.4.4 #2433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughBumps project/API version metadata from v1.4.3 to v1.4.4, removes a deprecated GC helper, refactors SnapshotQuery to use squirrel expressions, and adds extensive error-path tests across OIDC authn, schema walkers, engines, and many Postgres storage components; includes minor whitespace and helper relocations. Changes
Sequence Diagram(s)Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.2.2)Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/product/migration-guide for migration instructions ✨ Finishing Touches
🧪 Generate unit 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
internal/storage/postgres/utils/filter_test.go (2)
179-184: Also assert args ordering for UPDATE; it’s as important as SQL shape.Validating args prevents regressions in placeholder binding order.
- sql, _, err := sl.ToSql() + expectedArgs := []interface{}{1000, "1", "2", "organization", "admin", "4", "8", "user"} + sql, args, err := sl.ToSql() Expect(err).ShouldNot(HaveOccurred()) + Expect(args).Should(Equal(expectedArgs)) Expect(sql).Should(Equal(expectedSql))
230-235: Add args assertion for attributes UPDATE with filters.- sql, _, err := sl.ToSql() + expectedArgs := []interface{}{1000, "public", "balance", "1", "2", "organization"} + sql, args, err := sl.ToSql() Expect(err).ShouldNot(HaveOccurred()) + Expect(args).Should(Equal(expectedArgs)) Expect(sql).Should(Equal(expectedSql))internal/authn/oidc/authn_test.go (1)
520-521: Fix Gomega expectation misuseComparing error to the matcher is incorrect; assert occurrence instead.
-Expect(err).ShouldNot(Equal(BeNil())) +Expect(err).Should(HaveOccurred())
🧹 Nitpick comments (36)
internal/authn/preshared/authn_test.go (1)
41-44: Make assertions less brittle and assert returned authn is nil.Avoid exact string comparison on error messages and also verify the returned authenticator is nil for stronger guarantees.
Apply:
- _, err := NewKeyAuthn(context.Background(), emptyConfig) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("pre shared key authn must have at least one key")) + auth, err := NewKeyAuthn(context.Background(), emptyConfig) + Expect(err).To(HaveOccurred()) + Expect(auth).To(BeNil()) + Expect(err).To(MatchError(ContainSubstring("at least one key"))) - _, err := NewKeyAuthn(context.Background(), nilConfig) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("pre shared key authn must have at least one key")) + auth, err := NewKeyAuthn(context.Background(), nilConfig) + Expect(err).To(HaveOccurred()) + Expect(auth).To(BeNil()) + Expect(err).To(MatchError(ContainSubstring("at least one key")))Also applies to: 49-53
internal/storage/postgres/postgres_test.go (2)
4-4: Remove sort import if helper is deletedIf you switch to ConsistOf, drop the now-unused import.
-import ( - "sort" - "testing" +import ( + "testing"
16-37: ReplaceisSameArrayin postgres tests with Gomega’sConsistOfand delete the test helperIn
internal/storage/postgres/postgres_test.go, remove the localisSameArrayhelper and update assertions to:Expect(actual).To(ConsistOf(expected))Production still defines its own
isSameArrayinpkg/development/development.goandpkg/cmd/validate.go, so this change is limited to tests. Optionally consolidate the duplicate helpers in those packages into a shared utility.internal/storage/postgres/utils/filter_test.go (6)
84-98: Re-initialize the builder between assertions to avoid state leakage within the same test.The second scenario reuses the previously mutated
sl. Recreate the builder to keep cases independent.- sl = utils.TuplesFilterQueryForSelectBuilder(sl, filter) + // re-init builder for the second scenario + sl = squirrel.Select("*").From("relation_tuples") + sl = utils.TuplesFilterQueryForSelectBuilder(sl, filter)
101-101: Disambiguate duplicate context name.Two contexts are named “TestEmptyFilterQueryForSelectBuilder”. Rename this one to clarify it targets attributes.
- Context("TestEmptyFilterQueryForSelectBuilder", func() { + Context("TestEmptyAttributeFilterQueryForSelectBuilder", func() {
146-160: Re-initialize update builder before second scenario to prevent state carry-over.Same pattern here; recreate
slbefore the second call.- sl = utils.TuplesFilterQueryForUpdateBuilder(sl, filter) + // re-init builder for the second scenario + sl = squirrel.Update("relation_tuples").Set("expired_tx_id", 1000) + sl = utils.TuplesFilterQueryForUpdateBuilder(sl, filter)
208-215: Re-initialize attributes update builder before second scenario (consistency).- sl = utils.AttributesFilterQueryForUpdateBuilder(sl, filter) + // re-init builder for the second scenario + sl = squirrel.Update("attributes").Set("expired_tx_id", 1000) + sl = utils.AttributesFilterQueryForUpdateBuilder(sl, filter)
454-468: Assert args for single-attribute UPDATE as well.Keeps UPDATE tests consistent with SELECT tests.
- sql, _, err := sl.ToSql() + expectedArgs := []interface{}{1000, "single_attribute"} + sql, args, err := sl.ToSql() Expect(err).ShouldNot(HaveOccurred()) + Expect(args).Should(Equal(expectedArgs)) Expect(sql).Should(Equal(expectedSql))
492-731: Add missing nil-ID/attribute tests and subject_relation-only scenarios
- Add TuplesFilterQueryForUpdateBuilder tests for nil entity and nil subject IDs (to mirror SELECT nil cases).
- Add AttributesFilterQueryForUpdateBuilder test for nil attributes (parity with SELECT nil‐attribute case).
- Add tests covering subject_relation used alone and combined with a single subject ID.
- Since builders use a multi-key squirrel.Eq map, full-SQL assertions must lock clause order (or refactor builders to sequential Where calls for deterministic ordering).
internal/storage/postgres/utils/version_test.go (3)
3-14: Add strings import (used by env gating in multi-version test below).Preparing for safer multi-version gating needs
strings.import ( "os" "strconv" + "strings"
37-71: Consolidate redundant assertions into a single spec.The first four specs repeat the same setup and assertions. Merge for speed and clarity, and add a quick sanity check on idempotency.
- It("should return version for supported PostgreSQL version", func() { - version, err := utils.EnsureDBVersion(writePool) - Expect(err).ShouldNot(HaveOccurred()) - Expect(version).ShouldNot(BeEmpty()) - versionNum, parseErr := strconv.Atoi(version) - Expect(parseErr).ShouldNot(HaveOccurred()) - Expect(versionNum).Should(BeNumerically(">=", 130008)) - }) - - It("should return version string that can be parsed as integer", func() { - version, err := utils.EnsureDBVersion(writePool) - Expect(err).ShouldNot(HaveOccurred()) - Expect(version).ShouldNot(BeEmpty()) - versionNum, parseErr := strconv.Atoi(version) - Expect(parseErr).ShouldNot(HaveOccurred()) - Expect(versionNum).Should(BeNumerically(">", 0)) - }) - - It("should return version that meets minimum requirements", func() { - version, err := utils.EnsureDBVersion(writePool) - Expect(err).ShouldNot(HaveOccurred()) - Expect(version).ShouldNot(BeEmpty()) - versionNum, parseErr := strconv.Atoi(version) - Expect(parseErr).ShouldNot(HaveOccurred()) - Expect(versionNum).Should(BeNumerically(">=", 130008)) - }) - - It("should handle database connection properly", func() { - version, err := utils.EnsureDBVersion(writePool) - Expect(err).ShouldNot(HaveOccurred()) - Expect(version).ShouldNot(BeEmpty()) - Expect(version).Should(BeAssignableToTypeOf("")) - }) - - It("should return consistent results on multiple calls", func() { - version1, err1 := utils.EnsureDBVersion(writePool) - version2, err2 := utils.EnsureDBVersion(writePool) - Expect(err1).ShouldNot(HaveOccurred()) - Expect(err2).ShouldNot(HaveOccurred()) - Expect(version1).Should(Equal(version2)) - }) + It("returns a numeric server_version_num meeting minimum and is idempotent", func() { + v1, err := utils.EnsureDBVersion(writePool) + Expect(err).ShouldNot(HaveOccurred()) + Expect(v1).ShouldNot(BeEmpty()) + n, perr := strconv.Atoi(v1) + Expect(perr).ShouldNot(HaveOccurred()) + Expect(n).Should(BeNumerically(">=", 130008)) // 13.8 minimum + + v2, err2 := utils.EnsureDBVersion(writePool) + Expect(err2).ShouldNot(HaveOccurred()) + Expect(v2).Should(Equal(v1)) + })Also applies to: 73-80, 82-90
131-156: Reuse the suite’s writePool instead of creating another container; also tighten format assertions.Avoids an extra container spin and keeps the spec fast. Assert 6-digit length explicitly.
- version := os.Getenv("POSTGRES_VERSION") - if version == "" { - version = "14" - } - database := instance.PostgresDB(version) - db := database.(*PQDatabase.Postgres) - writePool := db.WritePool - versionStr, err := utils.EnsureDBVersion(writePool) - Expect(err).ShouldNot(HaveOccurred()) - versionNum, parseErr := strconv.Atoi(versionStr) - Expect(parseErr).ShouldNot(HaveOccurred()) - Expect(versionNum).Should(BeNumerically(">=", 100000)) - Expect(versionNum).Should(BeNumerically("<=", 999999)) - err = db.Close() - Expect(err).ShouldNot(HaveOccurred()) + versionStr, err := utils.EnsureDBVersion(writePool) + Expect(err).ShouldNot(HaveOccurred()) + versionNum, parseErr := strconv.Atoi(versionStr) + Expect(parseErr).ShouldNot(HaveOccurred()) + // 6 digits (MMmmpp) for server_version_num >= 10.0.0 + Expect(len(versionStr)).To(Equal(6)) + Expect(versionNum).Should(BeNumerically(">=", 100000)) + Expect(versionNum).Should(BeNumerically("<=", 999999))internal/info.go (1)
26-26: Version sync verified
Swagger JSON docs and proto openapi version both match “v1.4.4”. The only “v1.4.3” hits are indirect dependencies (github.com/go-logr/logr) in go.mod/go.sum, not the service version.
Optional: switch to build-time injection of Version via ldflags to prevent future drift.internal/schema/linkedSchema_test.go (1)
1448-1449: Consider documenting why these tests are skipped.While the skip messages explain the difficulty, it would be helpful to add TODO comments indicating whether these edge cases should be tested through other means (e.g., mocking, test helpers).
- Skip("Skipping test for undefined child type - difficult to trigger with normal schema compilation") + // TODO: Consider adding test helpers to create malformed schema structures for these edge cases + Skip("Skipping test for undefined child type - difficult to trigger with normal schema compilation")Also applies to: 1456-1457, 1464-1465
internal/storage/postgres/schemaReader_test.go (1)
255-259: Consider extracting common error assertion logic.The error assertion pattern is repeated many times. Consider creating a helper function to reduce duplication.
Add a helper function at the beginning of the test file:
func expectDatabaseError(err error) { Expect(err).Should(HaveOccurred()) Expect(err.Error()).Should(Or( Equal(base.ErrorCode_ERROR_CODE_SQL_BUILDER.String()), Equal(base.ErrorCode_ERROR_CODE_EXECUTION.String()), Equal(base.ErrorCode_ERROR_CODE_SCAN.String()), )) }Then simplify the assertions:
- Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Or( - Equal(base.ErrorCode_ERROR_CODE_SQL_BUILDER.String()), - Equal(base.ErrorCode_ERROR_CODE_EXECUTION.String()), - Equal(base.ErrorCode_ERROR_CODE_SCAN.String()), - )) + expectDatabaseError(err)Also applies to: 274-278, 293-297, 312-317, 334-338, 353-357, 372-376
internal/storage/postgres/tenantWriter_test.go (1)
99-116: Duplicate test descriptions could be more specific.Several tests have similar descriptions but test different error paths. Consider making the test names more specific to indicate which exact error path is being tested.
- It("should handle transaction begin error", func() { + It("should handle transaction begin error when starting delete operation", func() {- It("should handle batch execution error", func() { + It("should handle batch execution error during delete operations", func() {- It("should handle commit error after batch execution", func() { + It("should handle transaction commit error after successful batch execution", func() {Also applies to: 118-135, 137-154, 156-173, 175-192, 194-211
internal/storage/postgres/dataWriter_test.go (1)
545-548: Consider creating a test helper for common error assertions.Similar to the suggestion for schemaReader_test.go, the datastore error assertions are repeated frequently.
Add a helper function:
func expectDatastoreOrRetryError(err error) { Expect(err).Should(HaveOccurred()) Expect(err.Error()).Should(Or( Equal(base.ErrorCode_ERROR_CODE_DATASTORE.String()), Equal(base.ErrorCode_ERROR_CODE_ERROR_MAX_RETRIES.String()), )) }Then use it to simplify assertions:
- Expect(err).Should(HaveOccurred()) - // The error could be various types depending on when the connection fails - Expect(err.Error()).Should(Or( - Equal(base.ErrorCode_ERROR_CODE_DATASTORE.String()), - Equal(base.ErrorCode_ERROR_CODE_ERROR_MAX_RETRIES.String()), - )) + expectDatastoreOrRetryError(err)Also applies to: 569-572, 593-596, 617-620, 641-644
internal/storage/postgres/gc/gc_test.go (2)
361-366: Prefer errors.Is for context cancellation assertionUse errors.Is to be resilient to wrapped errors from Start.
- // Start the garbage collector with the cancelled context - err := garbageCollector.Start(ctx) - - Expect(err).Should(HaveOccurred()) - Expect(err).Should(Equal(context.Canceled)) + // Start the garbage collector with the cancelled context + err := garbageCollector.Start(ctx) + Expect(errors.Is(err, context.Canceled)).To(BeTrue())Add the import:
import "errors"
368-587: DRY the “closed DB” setup and honor POSTGRES_VERSIONEach test re-creates and closes a brand-new Postgres container with a hard-coded "14", which is slow and ignores POSTGRES_VERSION. Factor a small helper that reads the env and returns a closed DB; then reuse it across the specs.
Context("Error Handling", func() { + // Helper to provision a closed DB instance respecting POSTGRES_VERSION + getClosedDB := func() *PQDatabase.Postgres { + version := os.Getenv("POSTGRES_VERSION") + if version == "" { + version = "14" + } + d := instance.PostgresDB(version).(*PQDatabase.Postgres) + _ = d.Close() + return d + } @@ - // Create a garbage collector with a closed database connection - closedDB := instance.PostgresDB("14") - err := closedDB.Close() - Expect(err).ShouldNot(HaveOccurred()) - - badGC := NewGC( - closedDB.(*PQDatabase.Postgres), + badGC := NewGC( + getClosedDB(), Window(5*time.Second), )Apply the same pattern to the other tests in this context to remove duplication and container churn.
internal/storage/postgres/gc/options_test.go (1)
16-26: Avoid spinning a real Postgres for option settersThese tests only assert in-memory fields; using a real container slows CI and can flake. Use a lightweight stub Postgres instead and skip Close unless pools are non-nil.
- BeforeEach(func() { - version := "14" - db = instance.PostgresDB(version).(*PQDatabase.Postgres) - }) + BeforeEach(func() { + // No DB I/O needed for options; a zero-value stub is enough. + db = &PQDatabase.Postgres{} + }) @@ - AfterEach(func() { - if db != nil { - err := db.Close() - Expect(err).ShouldNot(HaveOccurred()) - } - }) + AfterEach(func() { + // Close only if pools exist (when running locally against a real DB). + if db != nil && (db.ReadPool != nil || db.WritePool != nil) { + err := db.Close() + Expect(err).ShouldNot(HaveOccurred()) + } + })internal/storage/postgres/bundleReader_test.go (3)
140-149: Apply the same dedicated-closed-DB pattern hereMirror the approach used above to avoid closing the shared db.
- closedDB := db.(*PQDatabase.Postgres) - err := closedDB.Close() - Expect(err).ShouldNot(HaveOccurred()) + version := os.Getenv("POSTGRES_VERSION") + if version == "" { + version = "14" + } + closedDB := instance.PostgresDB(version).(*PQDatabase.Postgres) + _ = closedDB.Close()
194-201: Repeat: isolate error injection from suite DBUse a fresh closed instance rather than closing the shared one.
- closedDB := db.(*PQDatabase.Postgres) - err := closedDB.Close() - Expect(err).ShouldNot(HaveOccurred()) + version := os.Getenv("POSTGRES_VERSION") + if version == "" { + version = "14" + } + closedDB := instance.PostgresDB(version).(*PQDatabase.Postgres) + _ = closedDB.Close()
120-129: Use a dedicated closed DB instance for error injection
Closing the shared suite DB (db) can disrupt other specs. Instead, instantiate a fresh Postgres instance viainstance.PostgresDB(version), close that, and pass it toNewBundleReader.Postgres.Close()unconditionally closes both pools and returns nil (idempotent).- // trigger SQL builder error on shared DB - closedDB := db.(*PQDatabase.Postgres) - err := closedDB.Close() - Expect(err).ShouldNot(HaveOccurred()) + // trigger SQL builder error on a dedicated closed DB + version := os.Getenv("POSTGRES_VERSION") + if version == "" { + version = "14" + } + closedDB := instance.PostgresDB(version).(*PQDatabase.Postgres) + _ = closedDB.Close()Ensure you import
"github.com/Permify/permify/internal/storage/postgres/instance"internal/storage/postgres/dataReader_test.go (3)
496-506: Extract a helper for a closed DB and avoid closing the suite DBMultiple specs close the shared db, which risks interfering with AfterEach or other specs. Add a small helper returning a dedicated closed DB (respecting POSTGRES_VERSION) and use it across the Error Handling context.
- Context("Error Handling", func() { + Context("Error Handling", func() { + getClosedDB := func() *PQDatabase.Postgres { + version := os.Getenv("POSTGRES_VERSION") + if version == "" { + version = "14" + } + d := instance.PostgresDB(version).(*PQDatabase.Postgres) + _ = d.Close() + return d + }
501-507: Use the helper instead of closing the shared dbReplace ad-hoc close with getClosedDB() to isolate failure injection.
- // Create a dataReader with a closed database to trigger errors - closedDB := db.(*PQDatabase.Postgres) - err := closedDB.Close() - Expect(err).ShouldNot(HaveOccurred()) - - readerWithClosedDB := NewDataReader(closedDB) + // Create a dataReader with a dedicated closed database + readerWithClosedDB := NewDataReader(getClosedDB())Apply the same change to the other specs in this Error Handling context that close db directly.
982-1000: HeadSnapshot SQL/scan error tests: also use the helperKeep the suite db open; inject failures via getClosedDB().
- // Create a dataReader with a closed database to trigger SQL builder error - closedDB := db.(*PQDatabase.Postgres) - err := closedDB.Close() - Expect(err).ShouldNot(HaveOccurred()) - - readerWithClosedDB := NewDataReader(closedDB) + // Create a dataReader with a dedicated closed database + readerWithClosedDB := NewDataReader(getClosedDB())internal/schema/walker_test.go (2)
206-212: Make error-code assertions resilient to wrappingDirect string equality on err.Error() is brittle if implementations wrap errors. Prefer matching by substring or a small helper to assert codes.
Apply this pattern (example for Lines 210, 235, 309, 341, 437, 469):
-Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_ENTITY_DEFINITION_NOT_FOUND.String())) +Expect(err).To(HaveOccurred()) +Expect(err.Error()).To(ContainSubstring(base.ErrorCode_ERROR_CODE_ENTITY_DEFINITION_NOT_FOUND.String()))Optionally add a tiny helper at file bottom and replace all call sites:
func expectErrCode(err error, code base.ErrorCode) { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(code.String())) }Also applies to: 233-236, 308-310, 340-342, 436-438, 468-470
257-262: Compare sentinel errors with errors.IsIf ErrUnimplemented might be wrapped later, equality checks can start failing. Use errors.Is to future-proof.
Example:
-Expect(err).Should(Equal(ErrUnimplemented)) +Expect(errors.Is(err, ErrUnimplemented)).To(BeTrue())Note: add
errorsimport.Also applies to: 361-368, 393-398
internal/authn/oidc/authn_test.go (1)
664-677: Skip rationale is fine; add link to code path for traceability (nit)Optional: reference the exact function/lines in authn.go in the Skip message to aid future maintainers.
internal/storage/postgres/watch_test.go (1)
232-246: Error-message coupling is fragile; keep Substring but consider categorizing by phaseThese getChanges error-path assertions depend on specific pgx messages ("encode text status undefined status"). If pgx changes text, tests will be noisy. Consider asserting higher-level phase (builder/exec/scan/unmarshal) via sentinel wrapping in production code, or at least grouping assertions with Or() for variations.
Also applies to: 248-262, 264-276, 278-291, 293-306, 308-321, 323-336
internal/storage/postgres/utils/common_test.go (6)
57-67: End spans to avoid leaks in longer runsAdd AfterEach to end the span created in BeforeEach.
Apply:
BeforeEach(func() { ctx = context.Background() _, span = noop.NewTracerProvider().Tracer("test").Start(ctx, "test-span") }) + AfterEach(func() { + span.End() + })
174-186: Tighten bounds to the known backoff window (reduce flakiness)Base backoff for retries=0 is 20ms with jitter up to +10ms. Use bounds that reflect this.
Apply:
- // Should have waited at least some time (backoff + jitter) - Expect(elapsed).Should(BeNumerically(">", 10*time.Millisecond)) - // But not too long (should be less than our timeout) - Expect(elapsed).Should(BeNumerically("<", 90*time.Millisecond)) + // 20ms <= elapsed < 100ms (with room for scheduler) + Expect(elapsed).Should(BeNumerically(">=", 18*time.Millisecond)) + Expect(elapsed).Should(BeNumerically("<", 100*time.Millisecond))
188-198: Cancellation threshold is tight; relax to avoid CI jitterImmediate select on ctx.Done() is near-instant but 10ms can be flaky on shared runners.
Apply:
- Expect(elapsed).Should(BeNumerically("<", 10*time.Millisecond)) + Expect(elapsed).Should(BeNumerically("<=", 30*time.Millisecond))
200-216: Strengthen assertion: higher retries must yield materially longer backoffGiven retries=2 ⇒ base ≥80ms (before jitter), assert a meaningful increase.
Apply:
- // Higher retry count should generally result in longer backoff - // (though jitter might make this not always true, so we use a reasonable threshold) - Expect(elapsed2).Should(BeNumerically(">=", time.Duration(float64(elapsed1)*0.5))) + // Higher retry count should yield a noticeably longer wait + Expect(elapsed2).Should(BeNumerically(">", elapsed1)) + Expect(elapsed2).Should(BeNumerically(">=", 60*time.Millisecond))
218-241: “Secure random” claim isn’t verifiable here; assert jittered range and uniqueness insteadCurrent equality check can still pass with identical durations by coincidence. Increase samples and assert range + uniqueness.
Apply:
- durations := make([]time.Duration, 5) - for i := 0; i < 5; i++ { + durations := make([]time.Duration, 10) + for i := 0; i < 10; i++ { start := time.Now() utils.WaitWithBackoff(ctx, "test-tenant", 0) durations[i] = time.Since(start) } - // Should have some variation in durations due to jitter - hasVariation := false - for i := 1; i < len(durations); i++ { - if durations[i] != durations[0] { - hasVariation = true - break - } - } - Expect(hasVariation).Should(BeTrue()) + // Each duration should be within the expected jittered window + for _, d := range durations { + Expect(d).Should(BeNumerically(">=", 18*time.Millisecond)) + Expect(d).Should(BeNumerically("<", 100*time.Millisecond)) + } + // And at least one should differ + unique := map[time.Duration]struct{}{} + for _, d := range durations { + unique[d] = struct{}{} + } + Expect(len(unique)).Should(BeNumerically(">", 1))Also consider renaming the test to “should introduce jitter (non-deterministic backoff)”.
285-314: LGTM: Span integration smoke testsGood sanity check that spans are used without panics. If needed later, introduce a tiny test span implementation to assert RecordError/SetStatus calls.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pkg/pb/base/v1/openapi.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (25)
docs/api-reference/apidocs.swagger.json(1 hunks)docs/api-reference/openapiv2/apidocs.swagger.json(1 hunks)internal/authn/oidc/adapter_test.go(1 hunks)internal/authn/oidc/authn_test.go(3 hunks)internal/authn/preshared/authn_test.go(1 hunks)internal/info.go(1 hunks)internal/schema/linkedSchema_test.go(1 hunks)internal/schema/walker_test.go(2 hunks)internal/storage/postgres/bundleReader_test.go(1 hunks)internal/storage/postgres/consts.go(0 hunks)internal/storage/postgres/dataReader_test.go(1 hunks)internal/storage/postgres/dataWriter_test.go(2 hunks)internal/storage/postgres/gc/gc.go(0 hunks)internal/storage/postgres/gc/gc_test.go(1 hunks)internal/storage/postgres/gc/options_test.go(1 hunks)internal/storage/postgres/postgres_test.go(2 hunks)internal/storage/postgres/schemaReader_test.go(1 hunks)internal/storage/postgres/tenantWriter_test.go(1 hunks)internal/storage/postgres/utils/common.go(0 hunks)internal/storage/postgres/utils/common_test.go(2 hunks)internal/storage/postgres/utils/filter_test.go(1 hunks)internal/storage/postgres/utils/version_test.go(1 hunks)internal/storage/postgres/watch.go(0 hunks)internal/storage/postgres/watch_test.go(2 hunks)proto/base/v1/openapi.proto(1 hunks)
💤 Files with no reviewable changes (4)
- internal/storage/postgres/watch.go
- internal/storage/postgres/gc/gc.go
- internal/storage/postgres/utils/common.go
- internal/storage/postgres/consts.go
🧰 Additional context used
🧬 Code graph analysis (14)
internal/schema/walker_test.go (5)
pkg/dsl/parser/parser.go (1)
NewParser(59-78)pkg/dsl/compiler/compiler.go (1)
NewCompiler(25-30)internal/schema/walker.go (1)
NewWalker(19-24)internal/schema/schema.go (1)
NewSchemaFromEntityAndRuleDefinitions(30-73)pkg/pb/base/v1/errors.pb.go (3)
ErrorCode_ERROR_CODE_ENTITY_DEFINITION_NOT_FOUND(70-70)ErrorCode_ERROR_CODE_UNDEFINED_CHILD_KIND(38-38)ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE(39-39)
internal/authn/preshared/authn_test.go (2)
internal/config/config.go (1)
Preshared(67-69)internal/authn/preshared/authn.go (1)
NewKeyAuthn(22-33)
internal/storage/postgres/gc/options_test.go (3)
pkg/database/postgres/postgres.go (1)
Postgres(22-35)internal/storage/postgres/instance/instance.go (1)
PostgresDB(20-89)internal/storage/postgres/gc/gc.go (1)
NewGC(31-45)
internal/storage/postgres/utils/common_test.go (2)
internal/storage/postgres/utils/common.go (4)
HandleError(101-124)IsContextRelatedError(127-137)IsSerializationRelatedError(140-146)WaitWithBackoff(150-168)pkg/pb/base/v1/errors.pb.go (3)
ErrorCode_ERROR_CODE_INTERNAL(82-82)ErrorCode_ERROR_CODE_CANCELLED(83-83)ErrorCode_ERROR_CODE_SERIALIZATION(100-100)
internal/storage/postgres/bundleReader_test.go (3)
pkg/database/postgres/postgres.go (1)
Postgres(22-35)internal/storage/postgres/bundleReader.go (1)
NewBundleReader(25-30)pkg/pb/base/v1/errors.pb.go (4)
ErrorCode_ERROR_CODE_SQL_BUILDER(84-84)ErrorCode_ERROR_CODE_SCAN(87-87)ErrorCode_ERROR_CODE_INVALID_ARGUMENT(58-58)ErrorCode_ERROR_CODE_CANCELLED(83-83)
internal/storage/postgres/utils/version_test.go (2)
pkg/database/postgres/postgres.go (1)
Postgres(22-35)internal/storage/postgres/instance/instance.go (1)
PostgresDB(20-89)
internal/schema/linkedSchema_test.go (3)
pkg/pb/base/v1/base.pb.go (3)
Entrance(1200-1209)Entrance(1224-1224)Entrance(1239-1241)internal/schema/linkedSchema.go (1)
NewLinkedGraph(30-34)internal/schema/schema.go (1)
NewSchemaFromEntityAndRuleDefinitions(30-73)
internal/storage/postgres/tenantWriter_test.go (2)
pkg/database/postgres/postgres.go (1)
Postgres(22-35)pkg/pb/base/v1/errors.pb.go (3)
ErrorCode_ERROR_CODE_EXECUTION(86-86)ErrorCode_ERROR_CODE_SQL_BUILDER(84-84)ErrorCode_ERROR_CODE_SCAN(87-87)
internal/storage/postgres/schemaReader_test.go (4)
pkg/database/postgres/postgres.go (1)
Postgres(22-35)internal/storage/postgres/schemaReader.go (1)
NewSchemaReader(30-35)pkg/pb/base/v1/errors.pb.go (6)
ErrorCode_ERROR_CODE_SQL_BUILDER(84-84)ErrorCode_ERROR_CODE_EXECUTION(86-86)ErrorCode_ERROR_CODE_SCAN(87-87)ErrorCode_ERROR_CODE_INTERNAL(82-82)ErrorCode_ERROR_CODE_SCHEMA_NOT_FOUND(68-68)ErrorCode_ERROR_CODE_INVALID_CONTINUOUS_TOKEN(54-54)pkg/database/pagination.go (3)
NewPagination(27-36)Size(7-11)Token(14-18)
internal/storage/postgres/gc/gc_test.go (3)
internal/storage/postgres/instance/instance.go (1)
PostgresDB(20-89)internal/storage/postgres/gc/gc.go (1)
NewGC(31-45)pkg/database/postgres/postgres.go (1)
Postgres(22-35)
internal/storage/postgres/dataWriter_test.go (3)
pkg/pb/base/v1/errors.pb.go (3)
ErrorCode_ERROR_CODE_MAX_DATA_PER_WRITE_EXCEEDED(63-63)ErrorCode_ERROR_CODE_DATASTORE(94-94)ErrorCode_ERROR_CODE_ERROR_MAX_RETRIES(90-90)pkg/database/postgres/postgres.go (1)
Postgres(22-35)internal/storage/postgres/dataWriter.go (1)
NewDataWriter(34-39)
internal/authn/oidc/authn_test.go (2)
internal/authn/oidc/authn.go (1)
NewOidcAuthn(53-111)internal/config/config.go (1)
Oidc(72-80)
internal/storage/postgres/dataReader_test.go (5)
pkg/pb/base/v1/base.pb.go (9)
TupleFilter(2038-2046)TupleFilter(2061-2061)TupleFilter(2076-2078)AttributeFilter(1982-1989)AttributeFilter(2004-2004)AttributeFilter(2019-2021)RelationReference(1143-1152)RelationReference(1167-1167)RelationReference(1182-1184)pkg/database/postgres/postgres.go (1)
Postgres(22-35)internal/storage/postgres/dataReader.go (1)
NewDataReader(35-40)pkg/database/pagination.go (6)
CursorPagination(52-56)NewCursorPagination(59-68)Cursor(71-75)Sort(78-82)Pagination(21-24)NewPagination(27-36)pkg/pb/base/v1/errors.pb.go (5)
ErrorCode_ERROR_CODE_INTERNAL(82-82)ErrorCode_ERROR_CODE_INVALID_CONTINUOUS_TOKEN(54-54)ErrorCode_ERROR_CODE_SQL_BUILDER(84-84)ErrorCode_ERROR_CODE_EXECUTION(86-86)ErrorCode_ERROR_CODE_SCAN(87-87)
internal/storage/postgres/watch_test.go (2)
internal/storage/postgres/watch.go (2)
Watch(26-35)NewWatcher(38-43)pkg/database/postgres/postgres.go (1)
Postgres(22-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Analyze (go)
- GitHub Check: Test with Coverage
- GitHub Check: Test with Coverage
🔇 Additional comments (26)
internal/authn/preshared/authn_test.go (1)
37-55: LGTM: solid coverage for empty and nil preshared keys.Tests accurately reflect NewKeyAuthn’s contract for invalid input.
internal/storage/postgres/utils/filter_test.go (1)
238-490: Nice additions: strong coverage for single-ID/attribute cases.The single-value paths (switching from IN to =) are well captured and improve confidence in builders.
internal/storage/postgres/utils/version_test.go (2)
1-2: Good choice to use an external test package.Testing via
utils_testenforces validation of public behavior only. LGTM.
92-121: Gate cross-version container tests behindPOSTGRES_VERSIONS
RequirePOSTGRES_VERSIONS(e.g., “13,14,15,16”) and skip tests otherwise to avoid brittle, heavy specs.- It("should handle different PostgreSQL versions", func() { - versions := []string{"13", "14", "15", "16"} - for _, pgVersion := range versions { - if os.Getenv("POSTGRES_VERSION") != "" && os.Getenv("POSTGRES_VERSION") != pgVersion { - continue - } + It("handles multiple PostgreSQL versions when explicitly requested", func() { + csv := os.Getenv("POSTGRES_VERSIONS") + if csv == "" { + Skip("set POSTGRES_VERSIONS to run cross-version container tests") + } + for _, pgVersion := range strings.Split(csv, ",") { database := instance.PostgresDB(pgVersion) testDB := database.(*PQDatabase.Postgres) testPool := testDB.WritePool version, err := utils.EnsureDBVersion(testPool) Expect(err).ShouldNot(HaveOccurred()) Expect(version).ShouldNot(BeEmpty()) versionNum, parseErr := strconv.Atoi(version) Expect(parseErr).ShouldNot(HaveOccurred()) Expect(versionNum).Should(BeNumerically(">=", 130008)) err = testDB.Close() Expect(err).ShouldNot(HaveOccurred()) } })Ensure CI workflows skip these specs unless
POSTGRES_VERSIONSis set or theintegrationlabel is applied.docs/api-reference/openapiv2/apidocs.swagger.json (1)
6-6: Version metadata bumped to v1.4.4 — LGTM.internal/info.go (1)
26-26: Update Version to v1.4.4 — looks good.docs/api-reference/apidocs.swagger.json (1)
6-6: Version field set to v1.4.4 — LGTM.proto/base/v1/openapi.proto (1)
12-12: OpenAPI info.version bumped to v1.4.4 — LGTM.internal/schema/linkedSchema_test.go (1)
1254-1615: LGTM! Comprehensive error handling test coverage added.The new error handling test context covers all major error scenarios for
LinkedEntrances:
- Permission/attribute not found
- Entity/relation definition not found
- Error propagation in recursive calls
The use of schema manipulation (deleting definitions) to trigger errors is a good testing approach.
internal/storage/postgres/schemaReader_test.go (1)
241-660: LGTM! Well-structured error handling tests for SchemaReader.The error handling test context follows a consistent pattern:
- Creating a closed DB connection to trigger errors
- Testing different error paths (SQL builder, execution, scan errors)
- Using
Ormatcher for multiple valid error codes- Testing schema-not-found scenarios with appropriate error codes
This approach provides good coverage for error paths.
internal/storage/postgres/tenantWriter_test.go (1)
76-213: LGTM! Comprehensive error handling for TenantWriter operations.Good coverage of various error scenarios in DeleteTenant including:
- Transaction begin/commit errors
- Batch execution/close errors
- Query row errors
The systematic approach to testing each failure point is excellent.
internal/storage/postgres/dataWriter_test.go (3)
5-5: LGTM! Required import for dynamic test data generation.The
fmtpackage import is necessary for generating test data in the error handling tests.
506-1116: Excellent comprehensive error handling test coverage!The error handling tests are well-organized with nested contexts for:
- Write operations (max data exceeded, serialization errors, retry logic)
- Delete operations (serialization errors, max retries)
- RunBundle operations (transaction errors)
- Internal error handling for each method
The tests effectively simulate various failure scenarios using closed DB connections.
508-525: Good test for max data per write validation.The test properly validates that attempting to write more than 1000 items returns the appropriate error code.
internal/authn/oidc/adapter_test.go (1)
1-153: Solid coverage and clear assertionsThe suite is readable and validates levels, messages, and kv-pairs well. The odd kv-pair case is a nice touch.
internal/schema/walker_test.go (1)
9-10: Import alias looks goodAlias for base error codes is appropriate and keeps assertions readable.
internal/authn/oidc/authn_test.go (4)
5-8: New imports are appropriateencoding/json and net/http are correctly added for custom OIDC config and HTTP error-path tests.
524-602: Solid backoff parameter validation testsGood coverage for zero/negative values across interval, max retries, and frequency.
736-820: OIDC configuration error-path tests look greatIssuer/JWKS validation and invalid JSON scenarios are well-covered.
822-858: HTTP status error-path test is correct404 case surfaces as expected via constructor fetch.
internal/storage/postgres/watch_test.go (1)
13-13: Import of types.XID8 is correctMatches getChanges signature and avoids ad-hoc definitions.
internal/storage/postgres/utils/common_test.go (5)
21-33: LGTM: SnapshotQuery test is precise and valuableExact SQL assertion is fine here given the function’s critical semantics.
68-109: LGTM: Context-related error handling cases cover the branchesTests correctly exercise cancellation, deadline, and "conn closed".
111-132: LGTM: Serialization and operational branches validatedGood coverage for both serialization and default error paths.
134-171: LGTM: Error detection helpers have comprehensive positive/negative casesSolid coverage and clear expectations.
36-55: No placeholder format mismatch — “?” is Squirrel’s default
These DeleteBuilders don’t call PlaceholderFormat, so they use Squirrel’s default question-mark placeholders. Tests correctly expect “?” and need no change.Likely an incorrect or invalid review comment.
| Context("Context Cancellation During Retry", func() { | ||
| It("should handle context cancellation during retry", func() { | ||
| // create authenticator with short backoff frequency to trigger retries quickly | ||
| ctx, cancel := context.WithCancel(context.Background()) | ||
| auth, err := NewOidcAuthn(ctx, config.Oidc{ | ||
| Audience: audience, | ||
| Issuer: issuerURL, | ||
| RefreshInterval: 5 * time.Minute, | ||
| BackoffInterval: 12 * time.Second, | ||
| BackoffMaxRetries: 3, | ||
| BackoffFrequency: 2 * time.Second, // Longer frequency to allow cancellation | ||
| }) | ||
| Expect(err).To(BeNil()) | ||
|
|
||
| // Create a token with invalid key ID to trigger retries | ||
| now := time.Now() | ||
| claims := jwt.RegisteredClaims{ | ||
| Issuer: issuerURL, | ||
| Subject: "user", | ||
| Audience: []string{audience}, | ||
| ExpiresAt: &jwt.NumericDate{Time: now.AddDate(1, 0, 0)}, | ||
| IssuedAt: &jwt.NumericDate{Time: now}, | ||
| } | ||
|
|
||
| unsignedToken := createUnsignedToken(claims, jwt.SigningMethodRS256) | ||
| unsignedToken.Header["kid"] = "invalidkeyid" // This will trigger retries | ||
| idToken, err := fakeOidcProvider.SignIDToken(unsignedToken) | ||
| Expect(err).To(BeNil()) | ||
|
|
||
| // Start authentication in a goroutine | ||
| authErr := make(chan error, 1) | ||
| go func() { | ||
| niceMd := make(metautils.NiceMD) | ||
| niceMd.Set("authorization", "Bearer "+idToken) | ||
| authErr <- auth.Authenticate(niceMd.ToIncoming(ctx)) | ||
| }() | ||
|
|
||
| // Cancel the context after a short delay to interrupt the retry | ||
| time.Sleep(100 * time.Millisecond) | ||
| cancel() | ||
|
|
||
| // Wait for the authentication to complete and check for context cancellation error | ||
| select { | ||
| case err := <-authErr: | ||
| Expect(err).To(HaveOccurred()) | ||
| // The error might be wrapped, so check for context cancellation in the error chain | ||
| Expect(err.Error()).To(Or( | ||
| ContainSubstring("context canceled"), | ||
| ContainSubstring("context cancelled"), | ||
| Equal(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()), | ||
| )) | ||
| case <-time.After(5 * time.Second): | ||
| Fail("Authentication should have completed or been cancelled") | ||
| } | ||
| }) | ||
| }) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Exercise true cancellation and relax assertion
This test cancels ctx, but assertions accept generic token errors too. Tighten it to actually validate cancellation signals.
Minimal tweak to assert cancellation (keeps current behavior if lib wraps):
- select {
- case err := <-authErr:
- Expect(err).To(HaveOccurred())
- // The error might be wrapped, so check for context cancellation in the error chain
- Expect(err.Error()).To(Or(
- ContainSubstring("context canceled"),
- ContainSubstring("context cancelled"),
- Equal(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()),
- ))
+ select {
+ case err := <-authErr:
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(Or(
+ ContainSubstring("context canceled"),
+ ContainSubstring("context cancelled"),
+ ))
case <-time.After(5 * time.Second):
Fail("Authentication should have completed or been cancelled")
}Consider shortening BackoffFrequency (e.g., 200ms) to reduce test wall time and flakiness.
📝 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.
| Context("Context Cancellation During Retry", func() { | |
| It("should handle context cancellation during retry", func() { | |
| // create authenticator with short backoff frequency to trigger retries quickly | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| auth, err := NewOidcAuthn(ctx, config.Oidc{ | |
| Audience: audience, | |
| Issuer: issuerURL, | |
| RefreshInterval: 5 * time.Minute, | |
| BackoffInterval: 12 * time.Second, | |
| BackoffMaxRetries: 3, | |
| BackoffFrequency: 2 * time.Second, // Longer frequency to allow cancellation | |
| }) | |
| Expect(err).To(BeNil()) | |
| // Create a token with invalid key ID to trigger retries | |
| now := time.Now() | |
| claims := jwt.RegisteredClaims{ | |
| Issuer: issuerURL, | |
| Subject: "user", | |
| Audience: []string{audience}, | |
| ExpiresAt: &jwt.NumericDate{Time: now.AddDate(1, 0, 0)}, | |
| IssuedAt: &jwt.NumericDate{Time: now}, | |
| } | |
| unsignedToken := createUnsignedToken(claims, jwt.SigningMethodRS256) | |
| unsignedToken.Header["kid"] = "invalidkeyid" // This will trigger retries | |
| idToken, err := fakeOidcProvider.SignIDToken(unsignedToken) | |
| Expect(err).To(BeNil()) | |
| // Start authentication in a goroutine | |
| authErr := make(chan error, 1) | |
| go func() { | |
| niceMd := make(metautils.NiceMD) | |
| niceMd.Set("authorization", "Bearer "+idToken) | |
| authErr <- auth.Authenticate(niceMd.ToIncoming(ctx)) | |
| }() | |
| // Cancel the context after a short delay to interrupt the retry | |
| time.Sleep(100 * time.Millisecond) | |
| cancel() | |
| // Wait for the authentication to complete and check for context cancellation error | |
| select { | |
| case err := <-authErr: | |
| Expect(err).To(HaveOccurred()) | |
| // The error might be wrapped, so check for context cancellation in the error chain | |
| Expect(err.Error()).To(Or( | |
| ContainSubstring("context canceled"), | |
| ContainSubstring("context cancelled"), | |
| Equal(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()), | |
| )) | |
| case <-time.After(5 * time.Second): | |
| Fail("Authentication should have completed or been cancelled") | |
| } | |
| }) | |
| }) | |
| // Wait for the authentication to complete and check for context cancellation error | |
| select { | |
| case err := <-authErr: | |
| Expect(err).To(HaveOccurred()) | |
| // Ensure we actually got a cancellation, not just a token error | |
| Expect(err.Error()).To(Or( | |
| ContainSubstring("context canceled"), | |
| ContainSubstring("context cancelled"), | |
| )) | |
| case <-time.After(5 * time.Second): | |
| Fail("Authentication should have completed or been cancelled") | |
| } |
🤖 Prompt for AI Agents
In internal/authn/oidc/authn_test.go around lines 679 to 735 the test cancels
the context but currently accepts generic token errors; change BackoffFrequency
to a much shorter value (e.g., 200*time.Millisecond) to speed retries and ensure
cancellation actually occurs, then tighten the assertion to require a
cancellation signal by checking errors.Is(err, context.Canceled) or scanning the
error chain for "context canceled"/"context cancelled" (and fail if only the
generic token error is present); keep the existing timeout guard but assert
cancellation explicitly so the test fails if only token errors are returned.
| It("should log context-related errors at debug level", func() { | ||
| var buf strings.Builder | ||
| logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})) | ||
| ctx := context.WithValue(context.Background(), "logger", logger) | ||
|
|
||
| // Create a cancelled context | ||
| cancelCtx, cancel := context.WithCancel(context.Background()) | ||
| cancel() | ||
|
|
||
| _, span := noop.NewTracerProvider().Tracer("test").Start(ctx, "test-span") | ||
| utils.HandleError(cancelCtx, span, errors.New("some error"), base.ErrorCode_ERROR_CODE_INTERNAL) | ||
|
|
||
| // Note: The actual logging happens in the HandleError function, | ||
| // but we can't easily capture it in this test setup. | ||
| // The test verifies the function doesn't panic and returns the expected error. | ||
| Expect(buf.String()).Should(ContainSubstring("")) | ||
| }) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Logging test currently asserts nothing; capture slog output and verify level/message
slog.*Context uses the default logger, not a logger in ctx. Set and restore the global default to capture logs and assert content.
Apply:
- var buf strings.Builder
- logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
- ctx := context.WithValue(context.Background(), "logger", logger)
+ var buf strings.Builder
+ prev := slog.Default()
+ defer slog.SetDefault(prev)
+ logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
+ slog.SetDefault(logger)
// Create a cancelled context
cancelCtx, cancel := context.WithCancel(context.Background())
cancel()
- _, span := noop.NewTracerProvider().Tracer("test").Start(ctx, "test-span")
+ _, span := noop.NewTracerProvider().Tracer("test").Start(context.Background(), "test-span")
utils.HandleError(cancelCtx, span, errors.New("some error"), base.ErrorCode_ERROR_CODE_INTERNAL)
- // Note: The actual logging happens in the HandleError function,
- // but we can't easily capture it in this test setup.
- // The test verifies the function doesn't panic and returns the expected error.
- Expect(buf.String()).Should(ContainSubstring(""))
+ Expect(buf.String()).Should(ContainSubstring("A context-related error occurred"))
+ Expect(buf.String()).Should(ContainSubstring("level=DEBUG"))📝 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.
| It("should log context-related errors at debug level", func() { | |
| var buf strings.Builder | |
| logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})) | |
| ctx := context.WithValue(context.Background(), "logger", logger) | |
| // Create a cancelled context | |
| cancelCtx, cancel := context.WithCancel(context.Background()) | |
| cancel() | |
| _, span := noop.NewTracerProvider().Tracer("test").Start(ctx, "test-span") | |
| utils.HandleError(cancelCtx, span, errors.New("some error"), base.ErrorCode_ERROR_CODE_INTERNAL) | |
| // Note: The actual logging happens in the HandleError function, | |
| // but we can't easily capture it in this test setup. | |
| // The test verifies the function doesn't panic and returns the expected error. | |
| Expect(buf.String()).Should(ContainSubstring("")) | |
| }) | |
| It("should log context-related errors at debug level", func() { | |
| var buf strings.Builder | |
| prev := slog.Default() | |
| defer slog.SetDefault(prev) | |
| logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})) | |
| slog.SetDefault(logger) | |
| // Create a cancelled context | |
| cancelCtx, cancel := context.WithCancel(context.Background()) | |
| cancel() | |
| _, span := noop.NewTracerProvider().Tracer("test").Start(context.Background(), "test-span") | |
| utils.HandleError(cancelCtx, span, errors.New("some error"), base.ErrorCode_ERROR_CODE_INTERNAL) | |
| Expect(buf.String()).Should(ContainSubstring("A context-related error occurred")) | |
| Expect(buf.String()).Should(ContainSubstring("level=DEBUG")) | |
| }) |
| It("should log serialization errors at debug level", func() { | ||
| ctx := context.Background() | ||
| _, span := noop.NewTracerProvider().Tracer("test").Start(ctx, "test-span") | ||
|
|
||
| err := utils.HandleError(ctx, span, errors.New("could not serialize access"), base.ErrorCode_ERROR_CODE_INTERNAL) | ||
|
|
||
| Expect(err).Should(HaveOccurred()) | ||
| Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_SERIALIZATION.String())) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Also assert serialization log path and level
Mirror the capture approach to validate debug-level serialization logging.
Apply:
- ctx := context.Background()
- _, span := noop.NewTracerProvider().Tracer("test").Start(ctx, "test-span")
-
- err := utils.HandleError(ctx, span, errors.New("could not serialize access"), base.ErrorCode_ERROR_CODE_INTERNAL)
+ var buf strings.Builder
+ prev := slog.Default()
+ defer slog.SetDefault(prev)
+ logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
+ slog.SetDefault(logger)
+ _, span := noop.NewTracerProvider().Tracer("test").Start(context.Background(), "test-span")
+
+ err := utils.HandleError(context.Background(), span, errors.New("could not serialize access"), base.ErrorCode_ERROR_CODE_INTERNAL)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_SERIALIZATION.String()))
+ Expect(buf.String()).Should(ContainSubstring("A serialization-related error occurred"))
+ Expect(buf.String()).Should(ContainSubstring("level=DEBUG"))Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
internal/storage/postgres/utils/common_test.go around lines 263 to 271: the test
verifies the returned error for serialization but does not assert that the
serialization path was logged at debug level; wrap the call in a log capture
(using the existing test logger harness or a test hook) before calling
utils.HandleError with the serialization error, then assert the captured log
contains the expected serialization message/path and that the log entry level is
debug (e.g., check message substring and level field), keeping existing error
assertions intact.
| It("should log operational errors at error level", func() { | ||
| ctx := context.Background() | ||
| _, span := noop.NewTracerProvider().Tracer("test").Start(ctx, "test-span") | ||
|
|
||
| operationalErr := errors.New("database connection failed") | ||
| err := utils.HandleError(ctx, span, operationalErr, base.ErrorCode_ERROR_CODE_INTERNAL) | ||
|
|
||
| Expect(err).Should(HaveOccurred()) | ||
| Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_INTERNAL.String())) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Assert operational errors are logged at error level
Capture slog and check both message and level.
Apply:
- ctx := context.Background()
- _, span := noop.NewTracerProvider().Tracer("test").Start(ctx, "test-span")
-
- operationalErr := errors.New("database connection failed")
- err := utils.HandleError(ctx, span, operationalErr, base.ErrorCode_ERROR_CODE_INTERNAL)
+ var buf strings.Builder
+ prev := slog.Default()
+ defer slog.SetDefault(prev)
+ logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
+ slog.SetDefault(logger)
+ _, span := noop.NewTracerProvider().Tracer("test").Start(context.Background(), "test-span")
+
+ operationalErr := errors.New("database connection failed")
+ err := utils.HandleError(context.Background(), span, operationalErr, base.ErrorCode_ERROR_CODE_INTERNAL)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_INTERNAL.String()))
+ Expect(buf.String()).Should(ContainSubstring("An operational error occurred"))
+ Expect(buf.String()).Should(ContainSubstring("level=ERROR"))📝 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.
| It("should log operational errors at error level", func() { | |
| ctx := context.Background() | |
| _, span := noop.NewTracerProvider().Tracer("test").Start(ctx, "test-span") | |
| operationalErr := errors.New("database connection failed") | |
| err := utils.HandleError(ctx, span, operationalErr, base.ErrorCode_ERROR_CODE_INTERNAL) | |
| Expect(err).Should(HaveOccurred()) | |
| Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_INTERNAL.String())) | |
| }) | |
| }) | |
| It("should log operational errors at error level", func() { | |
| // Set up a buffer to capture slog output, and restore the previous default logger afterwards | |
| var buf strings.Builder | |
| prev := slog.Default() | |
| defer slog.SetDefault(prev) | |
| logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})) | |
| slog.SetDefault(logger) | |
| // Start the span and invoke HandleError | |
| _, span := noop.NewTracerProvider().Tracer("test").Start(context.Background(), "test-span") | |
| operationalErr := errors.New("database connection failed") | |
| err := utils.HandleError(context.Background(), span, operationalErr, base.ErrorCode_ERROR_CODE_INTERNAL) | |
| // Validate the returned error as before | |
| Expect(err).Should(HaveOccurred()) | |
| Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_INTERNAL.String())) | |
| // New assertions: ensure the log contains the expected message and error level | |
| Expect(buf.String()).Should(ContainSubstring("An operational error occurred")) | |
| Expect(buf.String()).Should(ContainSubstring("level=ERROR")) | |
| }) |
🤖 Prompt for AI Agents
In internal/storage/postgres/utils/common_test.go around lines 273 to 283, the
test currently only asserts the returned error but does not capture or assert
structured slog output; modify the test to install a test slog handler (or
replace the global logger with a capturable handler) before calling
utils.HandleError, invoke the function with the operational error, then inspect
captured logs to assert there is a log entry whose message matches the
operational error context and whose level equals slog.LevelError (or "error");
finally restore the original logger and keep the existing error return
assertions.
| BeforeEach(func() { | ||
| version := os.Getenv("POSTGRES_VERSION") | ||
| if version == "" { | ||
| version = "14" | ||
| } | ||
|
|
||
| database := instance.PostgresDB(version) | ||
| db = database.(*PQDatabase.Postgres) | ||
| writePool = db.WritePool | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid per-spec container setup; switch to BeforeAll/AfterAll and reuse one DB for the suite.
Spinning containers in every It is slow and flaky. Initialize once and reuse the pool across tests.
- BeforeEach(func() {
+ BeforeAll(func() {
version := os.Getenv("POSTGRES_VERSION")
if version == "" {
version = "14"
}
database := instance.PostgresDB(version)
db = database.(*PQDatabase.Postgres)
writePool = db.WritePool
})
- AfterEach(func() {
+ AfterAll(func() {
err := db.Close()
Expect(err).ShouldNot(HaveOccurred())
})Also applies to: 32-35
| Context("Version Constants", func() { | ||
| It("should have correct minimum PostgreSQL version constant", func() { | ||
| // Test that the constant is set to the expected value | ||
| // This tests the constant definition indirectly | ||
| Expect(130008).Should(Equal(130008)) // earliestPostgresVersion | ||
| }) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove the self-equality constant test — it provides no signal.
Expect(130008).To(Equal(130008)) cannot fail and doesn’t validate repo behavior. Either expose the minimum as an exported constant (or getter) and assert against it, or delete this spec.
- It("should have correct minimum PostgreSQL version constant", func() {
- // Test that the constant is set to the expected value
- // This tests the constant definition indirectly
- Expect(130008).Should(Equal(130008)) // earliestPostgresVersion
- })📝 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.
| Context("Version Constants", func() { | |
| It("should have correct minimum PostgreSQL version constant", func() { | |
| // Test that the constant is set to the expected value | |
| // This tests the constant definition indirectly | |
| Expect(130008).Should(Equal(130008)) // earliestPostgresVersion | |
| }) | |
| Context("Version Constants", func() { | |
| // TODO: replace with a meaningful test that actually exercises | |
| // the exported earliestPostgresVersion constant or its getter | |
| }) |
🤖 Prompt for AI Agents
In internal/storage/postgres/utils/version_test.go around lines 124-130, the
test uses a self-equality assertion Expect(130008).To(Equal(130008)) which is
meaningless; either remove this spec or replace it to assert the actual
minimum-version symbol. Fix by referencing the package symbol (export the
constant or add a getter if needed) and change the test to
Expect(actualMinimumVersion).To(Equal(130008)), or simply delete the redundant
test block if exposing the constant is not desired.
| It("should handle get changes error", func() { | ||
| ctx := context.Background() | ||
|
|
||
| // Create a watcher with a closed database to trigger get changes error | ||
| closedDB := db.(*PQDatabase.Postgres) | ||
| err := closedDB.Close() | ||
| Expect(err).ShouldNot(HaveOccurred()) | ||
|
|
||
| watcherWithClosedDB := NewWatcher(closedDB) | ||
|
|
||
| // Use a valid snapshot format but with closed database | ||
| _, errs := watcherWithClosedDB.Watch(ctx, "t1", "0") | ||
|
|
||
| // Wait for error | ||
| select { | ||
| case err := <-errs: | ||
| Expect(err).Should(HaveOccurred()) | ||
| Expect(err.Error()).Should(ContainSubstring("illegal base64 data")) | ||
| case <-time.After(5 * time.Second): | ||
| Fail("Expected error but got timeout") | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test doesn’t validate “get changes” path; it re-hits base64 decode
Using "0" snapshot triggers base64 decode error before any DB call, so the test title and assertion are misleading.
Write once to obtain a valid snapshot, then close DB and expect a pool/exec error:
- // Create a watcher with a closed database to trigger get changes error
- closedDB := db.(*PQDatabase.Postgres)
- err := closedDB.Close()
- Expect(err).ShouldNot(HaveOccurred())
-
- watcherWithClosedDB := NewWatcher(closedDB)
-
- // Use a valid snapshot format but with closed database
- _, errs := watcherWithClosedDB.Watch(ctx, "t1", "0")
+ // Create a valid snapshot first
+ tup, terr := tuple.Tuple("organization:org-1#admin@user:user-1")
+ Expect(terr).ShouldNot(HaveOccurred())
+ tok, terr := dataWriter.Write(ctx, "t1",
+ database.NewTupleCollection(tup),
+ database.NewAttributeCollection(),
+ )
+ Expect(terr).ShouldNot(HaveOccurred())
+
+ // Now close DB to force downstream errors in getChanges
+ closedDB := db.(*PQDatabase.Postgres)
+ err := closedDB.Close()
+ Expect(err).ShouldNot(HaveOccurred())
+ watcherWithClosedDB := NewWatcher(closedDB)
+
+ _, errs := watcherWithClosedDB.Watch(ctx, "t1", tok.String())
...
- case err := <-errs:
- Expect(err).Should(HaveOccurred())
- Expect(err.Error()).Should(ContainSubstring("illegal base64 data"))
+ case err := <-errs:
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).Should(ContainSubstring("closed pool"))| It("should handle context cancellation", func() { | ||
| // Create a context that will be cancelled | ||
| ctx, cancel := context.WithCancel(context.Background()) | ||
|
|
||
| _, errs := watcher.Watch(ctx, "t1", "0") | ||
|
|
||
| // Cancel the context after a short delay | ||
| go func() { | ||
| time.Sleep(100 * time.Millisecond) | ||
| cancel() | ||
| }() | ||
|
|
||
| // Wait for cancellation error | ||
| select { | ||
| case err := <-errs: | ||
| Expect(err).Should(HaveOccurred()) | ||
| Expect(err.Error()).Should(ContainSubstring("illegal base64 data")) | ||
| case <-time.After(5 * time.Second): | ||
| Fail("Expected cancellation error but got timeout") | ||
| } | ||
|
|
||
| // Channels are closed by the Watch method internally | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Cancellation test isn’t asserting cancellation
Again, "0" snapshot triggers base64 error before cancellation takes effect. Provide a valid snapshot and assert cancel semantics.
Sketch:
- _, errs := watcher.Watch(ctx, "t1", "0")
+ // Obtain a valid snapshot
+ tup, terr := tuple.Tuple("organization:org-1#admin@user:user-1")
+ Expect(terr).ShouldNot(HaveOccurred())
+ tok, terr := dataWriter.Write(ctx, "t1",
+ database.NewTupleCollection(tup),
+ database.NewAttributeCollection(),
+ )
+ Expect(terr).ShouldNot(HaveOccurred())
+ _, errs := watcher.Watch(ctx, "t1", tok.String())
...
- case err := <-errs:
- Expect(err).Should(HaveOccurred())
- Expect(err.Error()).Should(ContainSubstring("illegal base64 data"))
+ case err := <-errs:
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(Or(
+ ContainSubstring("context canceled"),
+ ContainSubstring("context cancelled"),
+ ContainSubstring("user request"),
+ ))Optionally replace the select with Eventually(errs).Should(Receive()) for cleaner timing.
📝 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.
| It("should handle context cancellation", func() { | |
| // Create a context that will be cancelled | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| _, errs := watcher.Watch(ctx, "t1", "0") | |
| // Cancel the context after a short delay | |
| go func() { | |
| time.Sleep(100 * time.Millisecond) | |
| cancel() | |
| }() | |
| // Wait for cancellation error | |
| select { | |
| case err := <-errs: | |
| Expect(err).Should(HaveOccurred()) | |
| Expect(err.Error()).Should(ContainSubstring("illegal base64 data")) | |
| case <-time.After(5 * time.Second): | |
| Fail("Expected cancellation error but got timeout") | |
| } | |
| // Channels are closed by the Watch method internally | |
| }) | |
| It("should handle context cancellation", func() { | |
| // Create a context that will be cancelled | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| // Obtain a valid snapshot | |
| tup, terr := tuple.Tuple("organization:org-1#admin@user:user-1") | |
| Expect(terr).ShouldNot(HaveOccurred()) | |
| tok, terr := dataWriter.Write(ctx, "t1", | |
| database.NewTupleCollection(tup), | |
| database.NewAttributeCollection(), | |
| ) | |
| Expect(terr).ShouldNot(HaveOccurred()) | |
| _, errs := watcher.Watch(ctx, "t1", tok.String()) | |
| // Cancel the context after a short delay | |
| go func() { | |
| time.Sleep(100 * time.Millisecond) | |
| cancel() | |
| }() | |
| // Wait for cancellation error | |
| select { | |
| case err := <-errs: | |
| Expect(err).Should(HaveOccurred()) | |
| Expect(err.Error()).To(Or( | |
| ContainSubstring("context canceled"), | |
| ContainSubstring("context cancelled"), | |
| ContainSubstring("user request"), | |
| )) | |
| case <-time.After(5 * time.Second): | |
| Fail("Expected cancellation error but got timeout") | |
| } | |
| // Channels are closed by the Watch method internally | |
| }) |
🤖 Prompt for AI Agents
internal/storage/postgres/watch_test.go lines 160-182: the test uses snapshot
"0" which causes a base64 decode error before the context cancel takes effect;
replace the invalid snapshot with a valid snapshot value (e.g., a properly
base64-encoded snapshot or a known-good fixture used elsewhere in tests), cancel
the context as currently done, then assert that the received error is a
cancellation error (use errors.Is(err, context.Canceled) or check err.Error()
contains "context canceled") instead of asserting the base64 error; optionally
simplify timing by using Eventually(errs).Should(Receive()) to wait for the
error.
| It("should handle SQL builder error", func() { | ||
| ctx := context.Background() | ||
|
|
||
| // Create a watcher with a closed database to trigger SQL builder error | ||
| closedDB := db.(*PQDatabase.Postgres) | ||
| err := closedDB.Close() | ||
| Expect(err).ShouldNot(HaveOccurred()) | ||
|
|
||
| watcherWithClosedDB := NewWatcher(closedDB) | ||
|
|
||
| _, err = watcherWithClosedDB.getRecentXIDs(ctx, 0, "t1") | ||
| Expect(err).Should(HaveOccurred()) | ||
| Expect(err.Error()).Should(ContainSubstring("closed pool")) | ||
| }) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Closing shared db inside tests may break AfterEach
Several tests close db; AfterEach closes it again. If Close isn’t idempotent, teardown will fail.
Run to confirm Close behavior and references:
If Close isn’t idempotent, prefer creating a separate temp DB instance within each test solely to close, or refactor to a helper returning an already-closed *Postgres that doesn’t affect the shared db used in AfterEach.
Also applies to: 201-215, 216-230
🏁 Script executed:
#!/bin/bash
# Inspect Close() semantics and usages
rg -nP --type=go 'type\s+Postgres\s+struct' -C3
rg -nP --type=go 'func\s*\(\s*\*Postgres\s*\)\s*Close\s*\(' -C5
rg -nP --type=go 'AfterEach\(.*db\.Close' -nLength of output: 545
🏁 Script executed:
#!/bin/bash
# Locate Close method on Postgres struct
rg -nP --type=go 'func\s*\(\s*\*Postgres\s*\)\s*Close' -C5 pkg/database/postgres/postgres.go
# Fallback: search for any Close() invocation in pkg/database/postgres
rg -nP --type=go 'Close\(\)' -C3 pkg/database/postgres
# Inspect pool Close calls to infer idempotency
rg -nP --type=go 'ReadPool\.Close' -C3 pkg
rg -nP --type=go 'WritePool\.Close' -C3 pkg
# Locate any AfterEach teardown calling db.Close in watch tests
rg -nP 'AfterEach' -C3 internal/storage/postgres/watch_test.goLength of output: 3078
Do not close the shared db in this spec; use a separate closed instance
Postgres.Close() unconditionally calls ReadPool.Close() and WritePool.Close() (pkg/database/postgres/postgres.go:137–141), so closing the shared db in the test then again in AfterEach risks double-close panics or silent failures. Instead, for the “SQL builder error” case, instantiate a dedicated *Postgres (or use the MockPQDatabase) that’s already closed, and leave the shared db untouched.
🤖 Prompt for AI Agents
internal/storage/postgres/watch_test.go around lines 186 to 200: the test
currently closes the shared `db` to simulate an SQL builder error; instead,
create a new dedicated `*Postgres` (or use the MockPQDatabase) instance, call
Close() on that dedicated instance, and pass it to NewWatcher so the shared `db`
remains untouched; update the spec to use this closed dedicated instance for
watcherWithClosedDB and remove any closing of the shared `db` in this test to
avoid double-close issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
internal/authn/oidc/authn_test.go (1)
679-733: Assert true cancellation and shorten backoff to reduce flakinessTighten the assertion to require cancellation (don’t accept generic token errors) and increase test determinism by shortening backoff frequency. This also speeds the test.
- auth, err := NewOidcAuthn(ctx, config.Oidc{ + auth, err := NewOidcAuthn(ctx, config.Oidc{ Audience: audience, Issuer: issuerURL, RefreshInterval: 5 * time.Minute, BackoffInterval: 12 * time.Second, BackoffMaxRetries: 3, - BackoffFrequency: 2 * time.Second, // Longer frequency to allow cancellation + BackoffFrequency: 200 * time.Millisecond, }) @@ - time.Sleep(100 * time.Millisecond) + time.Sleep(300 * time.Millisecond) cancel() @@ - case err := <-authErr: - Expect(err).To(HaveOccurred()) - // The error might be wrapped, so check for context cancellation in the error chain - Expect(err.Error()).To(Or( - ContainSubstring("context canceled"), - ContainSubstring("context cancelled"), - Equal(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()), - )) + case err := <-authErr: + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Or( + ContainSubstring("context canceled"), + ContainSubstring("context cancelled"), + ))
🧹 Nitpick comments (2)
internal/authn/oidc/authn_test.go (2)
524-602: Reduce brittleness in error assertions; consider DRYing the casesExact string equality on errors is brittle. Prefer substring match. Optionally, collapse these into a table-driven loop to avoid repetition.
Apply this minimal change to loosen assertions:
- Expect(err.Error()).To(Equal("invalid or missing backoffInterval")) + Expect(err.Error()).To(ContainSubstring("invalid or missing backoffInterval")) - Expect(err.Error()).To(Equal("invalid or missing backoffInterval")) + Expect(err.Error()).To(ContainSubstring("invalid or missing backoffInterval")) - Expect(err.Error()).To(Equal("invalid or missing backoffMaxRetries")) + Expect(err.Error()).To(ContainSubstring("invalid or missing backoffMaxRetries")) - Expect(err.Error()).To(Equal("invalid or missing backoffMaxRetries")) + Expect(err.Error()).To(ContainSubstring("invalid or missing backoffMaxRetries")) - Expect(err.Error()).To(Equal("invalid or missing backoffFrequency")) + Expect(err.Error()).To(ContainSubstring("invalid or missing backoffFrequency")) - Expect(err.Error()).To(Equal("invalid or missing backoffFrequency")) + Expect(err.Error()).To(ContainSubstring("invalid or missing backoffFrequency"))
664-676: Prefer pending test (XIt/PIt) over runtime SkipMarking the spec pending avoids executing the body and communicates intent more clearly.
- It("should return error for invalid token claims format", func() { + XIt("should return error for invalid token claims format", func() { @@ - Skip("Invalid claims format test - JWT library automatically converts to MapClaims") })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
internal/authn/oidc/authn_test.go(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/authn/oidc/authn_test.go (2)
internal/authn/oidc/authn.go (1)
NewOidcAuthn(53-111)internal/config/config.go (1)
Oidc(72-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Test with Coverage
- GitHub Check: Analyze (go)
- GitHub Check: Test with Coverage
🔇 Additional comments (4)
internal/authn/oidc/authn_test.go (4)
5-8: LGTM: importsBoth json and http are used by new negative-path tests; import additions are correct.
736-820: LGTM: robust OIDC discovery error coverageGood negative-path tests for missing issuer/JWKs and invalid JSON.
822-858: LGTM: HTTP error handling testsCovers non-200 and invalid issuer URL paths with clear assertions.
860-889: LGTM: guarded Hijacker-based read error simulationNice addition of the Hijacker capability check to avoid flakiness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🧹 Nitpick comments (13)
internal/engines/expand_test.go (3)
1583-1587: Avoid brittle exact error string comparisonsComparing
err.Error()tobase.ErrorCode_ERROR_CODE_SCHEMA_NOT_FOUND.String()is fragile (wrapping/formatting may change). Prefer matching by code or using substring.Apply:
- Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_SCHEMA_NOT_FOUND.String())) + Expect(err.Error()).Should(ContainSubstring(base.ErrorCode_ERROR_CODE_SCHEMA_NOT_FOUND.String()))If there’s a typed error exposing a code, compare codes directly instead of strings.
Also applies to: 1613-1615
1590-1604: “context cancellation” test doesn’t validate cancellationThe failure is due to missing schema, not cancellation. Either write a minimal schema so the code reaches a cancelable path, and assert
context.Canceled/status, or drop/rename this test.Also applies to: 1609-1615
1520-1520: Remove hardcoded source line references in commentsInline “(line …)” notes will drift. They add maintenance noise without test value.
- // Test entity definition read error (line 77) + // Test entity definition read error(Repeat for other instances in this context.)
Also applies to: 1552-1552, 1576-1576, 1601-1601, 1618-1618, 1628-1628, 1644-1644, 1659-1659
internal/engines/lookup_test.go (3)
5016-5020: Don’t pass nil stream sink
LookupEntityStreamreceives a nil sink; the test relies on failing earlier. Pass a no-op sink to decouple from call-order.- err = engine.LookupEntityStream(context.Background(), request, nil) + sink := func(*base.PermissionLookupEntityStreamResponse) error { return nil } + err = engine.LookupEntityStream(context.Background(), request, sink)
24-46: Local mockSchemaReader OK; consider reuseThe stub is fine. If multiple packages need this, consider moving it to a shared test helper to reduce duplication.
4955-4958: Message substring check is fine; prefer sentinel error if availableUsing “failed to create bulk checker” ties to wording. If the engine exposes a sentinel error, switch to
errors.Is.internal/engines/bulk_test.go (7)
63-65: Avoid brittle string equality for errorsExact string matches are fragile. Prefer MatchError with substring.
- Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("context cannot be nil")) + Expect(err).Should(MatchError(ContainSubstring("context cannot be nil")))- Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("callback cannot be nil")) + Expect(err).Should(MatchError(ContainSubstring("callback cannot be nil")))Also applies to: 76-78
191-193: Same: use MatchError instead of string equality- Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("size must be greater than 0")) + Expect(err).Should(MatchError(ContainSubstring("size must be greater than 0")))
237-239: Fix misleading commentThis creates a checker whose Check returns an error; the callback isn’t returning an error.
- // Create a checker with a callback that returns an error + // Create a checker whose underlying engine returns an error
124-186: Consider edge cases in sortRequests testsAdd cases for: already-sorted input (stability), duplicate IDs, and nil Entity/Subject to guard against panics.
I can add these tests if you want.
213-216: Strengthen success-path coverageCurrently ExecuteRequests(1) runs with no queued requests. Consider publishing at least one request (via a publisher or directly pushing to checker.requestChan) and asserting the callback is invoked.
I can draft a minimal test that enqueues one request and waits with Eventually on a callback counter.
48-103: Add missing validations: nil checker and negative config valuesExpand NewBulkChecker tests to cover nil checker error and negative ConcurrencyLimit/BufferSize defaulting.
Context("NewBulkChecker", func() { var mockChecker invoke.Check BeforeEach(func() { mockChecker = &mockCheckEngine{} }) + It("should return error for nil checker", func() { + ctx := context.Background() + config := BulkCheckerConfig{ConcurrencyLimit: 5, BufferSize: 100} + _, err := NewBulkChecker(ctx, nil, BulkCheckerTypeEntity, func(string, string) {}, config) + Expect(err).Should(MatchError(ContainSubstring("checker cannot be nil"))) + }) + + It("should default negative config values", func() { + ctx := context.Background() + config := BulkCheckerConfig{ConcurrencyLimit: -1, BufferSize: -5} + chk, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(string, string) {}, config) + Expect(err).ShouldNot(HaveOccurred()) + DeferCleanup(chk.cancel) + Expect(chk.config.ConcurrencyLimit).Should(Equal(10)) + Expect(chk.config.BufferSize).Should(Equal(1000)) + })
41-45: Optional: don’t over-specify defaultsAsserting concrete numbers can cause churn if defaults tune over time. If the goal is “non-zero sensible defaults,” consider BeNumerically(">=", 1) instead.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
internal/engines/bulk_test.go(1 hunks)internal/engines/expand_test.go(1 hunks)internal/engines/lookup_test.go(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
internal/engines/lookup_test.go (2)
pkg/pb/base/v1/service.pb.go (12)
PermissionLookupEntityRequest(505-531)PermissionLookupEntityRequest(546-546)PermissionLookupEntityRequest(561-563)PermissionLookupEntityRequestMetadata(629-640)PermissionLookupEntityRequestMetadata(655-655)PermissionLookupEntityRequestMetadata(670-672)PermissionLookupSubjectRequest(984-1009)PermissionLookupSubjectRequest(1024-1024)PermissionLookupSubjectRequest(1039-1041)PermissionLookupSubjectRequestMetadata(1107-1118)PermissionLookupSubjectRequestMetadata(1133-1133)PermissionLookupSubjectRequestMetadata(1148-1150)internal/engines/lookup.go (1)
NewLookupEngine(31-51)
internal/engines/bulk_test.go (2)
pkg/pb/base/v1/base.pb.go (2)
CheckResult_CHECK_RESULT_ALLOWED(35-35)CheckResult_CHECK_RESULT_UNSPECIFIED(33-33)internal/engines/bulk.go (9)
DefaultBulkCheckerConfig(56-61)BulkCheckerConfig(44-52)NewBulkChecker(130-170)BulkCheckerTypeEntity(28-28)BulkChecker(69-98)BulkCheckerRequest(34-39)BulkCheckerTypeSubject(26-26)NewBulkEntityPublisher(497-502)NewBulkSubjectPublisher(552-557)
internal/engines/expand_test.go (4)
pkg/pb/base/v1/base.pb.go (12)
Context(421-432)Context(447-447)Context(462-464)Entity(1806-1813)Entity(1828-1828)Entity(1843-1845)Expand(2279-2297)Expand(2312-2312)Expand(2327-2329)Argument(1258-1267)Argument(1282-1282)Argument(1297-1299)internal/engines/expand.go (3)
NewExpandEngine(30-35)ExpandResponse(57-60)ExpandFunction(64-64)pkg/pb/base/v1/service.pb.go (3)
PermissionExpandRequest(304-321)PermissionExpandRequest(336-336)PermissionExpandRequest(351-353)pkg/pb/base/v1/errors.pb.go (1)
ErrorCode_ERROR_CODE_SCHEMA_NOT_FOUND(68-68)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Analyze (go)
- GitHub Check: Test with Coverage
- GitHub Check: Test with Coverage
🔇 Additional comments (3)
internal/engines/expand_test.go (1)
1518-1530: Name vs. behavior mismatchTest name says “entity definition read errors” but the setup triggers “schema not found.” Either write a schema and request a non-existent entity to actually hit entity-def read, or rename.
internal/engines/lookup_test.go (1)
4928-5103: No action needed for mockCheckEngine or nil callbacks The references tomockCheckEngineinlookup_test.goand thenilarguments passed toLookupEntityStreamare deliberate in these tests to simulate error conditions; ignore the original suggestion.Likely an incorrect or invalid review comment.
internal/engines/bulk_test.go (1)
265-293: Leave ctx parameter in NewBulkEntityPublisher/NewBulkSubjectPublisher
The constructors accept a context but don’t store it by design (see bulk.go parameter docs at lines 491–492 and 546–547: “ctx: Context for the publisher (currently unused but kept for API consistency)”). No changes required.
| checker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | ||
|
|
||
| Expect(err).ShouldNot(HaveOccurred()) | ||
| Expect(checker.config.ConcurrencyLimit).Should(Equal(10)) // Default value | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Cleanup created BulkChecker instances in tests
Use DeferCleanup to ensure cancellation even on failure, preventing leaks.
checker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config)
+ DeferCleanup(checker.cancel)📝 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.
| checker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| Expect(checker.config.ConcurrencyLimit).Should(Equal(10)) // Default value | |
| }) | |
| checker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| DeferCleanup(checker.cancel) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| Expect(checker.config.ConcurrencyLimit).Should(Equal(10)) // Default value | |
| }) |
🤖 Prompt for AI Agents
In internal/engines/bulk_test.go around lines 86 to 90, register a DeferCleanup
right after creating the checker to ensure it gets cancelled on test failure:
after NewBulkChecker returns, call DeferCleanup(func(){ if checker != nil {
checker.Cancel() } }) (or checker.Close()/checker.Stop() if that is the actual
teardown method on BulkChecker) so the checker is cleaned up even when
assertions fail.
| checker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | ||
|
|
||
| Expect(err).ShouldNot(HaveOccurred()) | ||
| Expect(checker.config.BufferSize).Should(Equal(1000)) // Default value | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add DeferCleanup here as well
checker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config)
+ DeferCleanup(checker.cancel)📝 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.
| checker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| Expect(checker.config.BufferSize).Should(Equal(1000)) // Default value | |
| }) | |
| checker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| DeferCleanup(checker.cancel) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| Expect(checker.config.BufferSize).Should(Equal(1000)) // Default value | |
| }) |
🤖 Prompt for AI Agents
In internal/engines/bulk_test.go around lines 98 to 102, the test creates a
BulkChecker but does not register a cleanup, so add a DeferCleanup call after
creation to ensure the checker is torn down; e.g., immediately after
NewBulkChecker succeeds call DeferCleanup with the checker’s cleanup method (for
example DeferCleanup(checker.Close) or DeferCleanup(checker.Stop) depending on
the BulkChecker API) so resources are released after the test.
| BeforeEach(func() { | ||
| ctx = context.Background() | ||
| config := BulkCheckerConfig{ | ||
| ConcurrencyLimit: 2, | ||
| BufferSize: 10, | ||
| } | ||
|
|
||
| mockChecker = &mockCheckEngine{} | ||
|
|
||
| var err error | ||
| checker, err = NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | ||
| Expect(err).ShouldNot(HaveOccurred()) | ||
| }) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Cancel the BulkChecker context to avoid goroutine leaks
NewBulkChecker starts a background collector; without canceling, tests can leak goroutines. Add an AfterEach that calls checker.cancel().
BeforeEach(func() {
ctx = context.Background()
config := BulkCheckerConfig{
ConcurrencyLimit: 2,
BufferSize: 10,
}
mockChecker = &mockCheckEngine{}
var err error
checker, err = NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config)
Expect(err).ShouldNot(HaveOccurred())
})
+
+ AfterEach(func() {
+ if checker != nil && checker.cancel != nil {
+ checker.cancel()
+ }
+ })📝 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.
| BeforeEach(func() { | |
| ctx = context.Background() | |
| config := BulkCheckerConfig{ | |
| ConcurrencyLimit: 2, | |
| BufferSize: 10, | |
| } | |
| mockChecker = &mockCheckEngine{} | |
| var err error | |
| checker, err = NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| }) | |
| BeforeEach(func() { | |
| ctx = context.Background() | |
| config := BulkCheckerConfig{ | |
| ConcurrencyLimit: 2, | |
| BufferSize: 10, | |
| } | |
| mockChecker = &mockCheckEngine{} | |
| var err error | |
| checker, err = NewBulkChecker(ctx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| }) | |
| AfterEach(func() { | |
| if checker != nil && checker.cancel != nil { | |
| checker.cancel() | |
| } | |
| }) |
🤖 Prompt for AI Agents
In internal/engines/bulk_test.go around lines 110 to 123, the test's BeforeEach
creates a BulkChecker that starts a background collector but the context is
never canceled; add an AfterEach that calls checker.cancel() (or checker.cancel
if it's a method/field) to ensure the background goroutine is stopped; guard the
call with a nil check (e.g., if checker != nil { checker.cancel() }) to avoid
panics and place the AfterEach directly after the BeforeEach in the same
Describe/Context block.
| mockChecker := &mockCheckEngine{} | ||
| subjectChecker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeSubject, func(entityID, ct string) {}, config) | ||
| Expect(err).ShouldNot(HaveOccurred()) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Defer cleanup for subjectChecker
subjectChecker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeSubject, func(entityID, ct string) {}, config)
Expect(err).ShouldNot(HaveOccurred())
+ DeferCleanup(subjectChecker.cancel)📝 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.
| mockChecker := &mockCheckEngine{} | |
| subjectChecker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeSubject, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| mockChecker := &mockCheckEngine{} | |
| subjectChecker, err := NewBulkChecker(ctx, mockChecker, BulkCheckerTypeSubject, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| DeferCleanup(subjectChecker.cancel) |
🤖 Prompt for AI Agents
In internal/engines/bulk_test.go around lines 158 to 161, the test creates
subjectChecker via NewBulkChecker but does not schedule cleanup; add a deferred
cleanup call immediately after the successful creation (e.g., defer
subjectChecker.Close()) so the checker is shut down at test end and resources
are released.
| cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | ||
| Expect(err).ShouldNot(HaveOccurred()) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Defer cleanup for cancelledChecker (entity path)
cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config)
Expect(err).ShouldNot(HaveOccurred())
+ DeferCleanup(cancelledChecker.cancel)📝 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.
| cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| DeferCleanup(cancelledChecker.cancel) |
🤖 Prompt for AI Agents
In internal/engines/bulk_test.go around lines 205–207, the test creates
cancelledChecker but does not defer its cleanup; immediately after the
NewBulkChecker call add a deferred cleanup call (e.g., defer
cancelledChecker.Close() or defer cancelledChecker.Stop() using the actual
cleanup method on BulkChecker) to ensure resources are released even if the test
fails.
| errorBulkChecker, err := NewBulkChecker(ctx, errorChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | ||
| Expect(err).ShouldNot(HaveOccurred()) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Defer cleanup for errorBulkChecker
errorBulkChecker, err := NewBulkChecker(ctx, errorChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config)
Expect(err).ShouldNot(HaveOccurred())
+ DeferCleanup(errorBulkChecker.cancel)📝 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.
| errorBulkChecker, err := NewBulkChecker(ctx, errorChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| errorBulkChecker, err := NewBulkChecker(ctx, errorChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| DeferCleanup(errorBulkChecker.cancel) |
🤖 Prompt for AI Agents
internal/engines/bulk_test.go around lines 245 to 247: the test creates
errorBulkChecker but doesn't clean it up; add a defer immediately after creation
to call its cleanup method (e.g., defer errorBulkChecker.Close()) or, if the
type exposes Stop/Shutdown, call that instead (wrap with a nil-check or ignore
the returned error with _ = ... to avoid test failures).
| cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | ||
| Expect(err).ShouldNot(HaveOccurred()) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Defer cleanup for cancelledChecker (entity publisher)
cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config)
Expect(err).ShouldNot(HaveOccurred())
+ DeferCleanup(cancelledChecker.cancel)📝 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.
| cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeEntity, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| DeferCleanup(cancelledChecker.cancel) |
🤖 Prompt for AI Agents
In internal/engines/bulk_test.go around lines 278 to 280, the created
cancelledChecker (entity publisher) is not cleaned up; add a defer to close it
immediately after creation to avoid resource leakage: after the
Expect(err).ShouldNot(HaveOccurred()) line, add defer cancelledChecker.Close()
(or the appropriate teardown method such as Stop()/Shutdown() if Close() is not
available) so the checker is properly stopped at test end.
| cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeSubject, func(entityID, ct string) {}, config) | ||
| Expect(err).ShouldNot(HaveOccurred()) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Defer cleanup for cancelledChecker (subject publisher)
cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeSubject, func(entityID, ct string) {}, config)
Expect(err).ShouldNot(HaveOccurred())
+ DeferCleanup(cancelledChecker.cancel)📝 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.
| cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeSubject, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| cancelledChecker, err := NewBulkChecker(cancelCtx, mockChecker, BulkCheckerTypeSubject, func(entityID, ct string) {}, config) | |
| Expect(err).ShouldNot(HaveOccurred()) | |
| DeferCleanup(cancelledChecker.cancel) |
🤖 Prompt for AI Agents
In internal/engines/bulk_test.go around lines 308 to 310, the newly created
cancelledChecker (subject publisher) is not cleaned up; add a deferred close
after creation to release resources: immediately after the
Expect(err).ShouldNot(HaveOccurred()) line, add defer cancelledChecker.Close()
(or the appropriate teardown method on BulkChecker such as Stop/Shutdown if
Close() does not exist) so the subject publisher is properly cleaned up at test
end.
| // Test expandIntersection function (line 853) | ||
| entity := &base.Entity{Type: "user", Id: "user1"} | ||
| permission := "read" | ||
| arguments := []*base.Argument{} | ||
| functions := []ExpandFunction{} | ||
|
|
||
| response := expandIntersection(context.Background(), entity, permission, arguments, functions) | ||
| Expect(response).ShouldNot(BeNil()) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test always passes; struct value can’t be nil
expandIntersection returns a value (not a pointer). Expect(response).ShouldNot(BeNil()) will always succeed. Assert on fields instead.
Apply:
- response := expandIntersection(context.Background(), entity, permission, arguments, functions)
- Expect(response).ShouldNot(BeNil())
+ resp := expandIntersection(context.Background(), entity, permission, arguments, functions)
+ Expect(resp.Err).ShouldNot(HaveOccurred())
+ Expect(resp.Response).ShouldNot(BeNil())
+ Expect(resp.Response.Tree).ShouldNot(BeNil())📝 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.
| // Test expandIntersection function (line 853) | |
| entity := &base.Entity{Type: "user", Id: "user1"} | |
| permission := "read" | |
| arguments := []*base.Argument{} | |
| functions := []ExpandFunction{} | |
| response := expandIntersection(context.Background(), entity, permission, arguments, functions) | |
| Expect(response).ShouldNot(BeNil()) | |
| }) | |
| // Test expandIntersection function (line 853) | |
| entity := &base.Entity{Type: "user", Id: "user1"} | |
| permission := "read" | |
| arguments := []*base.Argument{} | |
| functions := []ExpandFunction{} | |
| resp := expandIntersection(context.Background(), entity, permission, arguments, functions) | |
| Expect(resp.Err).ShouldNot(HaveOccurred()) | |
| Expect(resp.Response).ShouldNot(BeNil()) | |
| Expect(resp.Response.Tree).ShouldNot(BeNil()) | |
| }) |
🤖 Prompt for AI Agents
In internal/engines/expand_test.go around lines 1659 to 1667, the test calls
expandIntersection which returns a non-pointer struct, so
Expect(response).ShouldNot(BeNil()) always succeeds; change the assertion to
validate the struct's contents instead. Replace the nil check with assertions
against meaningful fields (for example, check response.Error is nil/empty,
response.Permissions length, or specific expected values in response) or use
deep equality to an expected struct instance; ensure you construct the expected
result or assert on concrete fields that prove expandIntersection behaved
correctly.
| mockCheckEngine := &mockCheckEngine{} | ||
| engine := NewLookupEngine(mockCheckEngine, schemaReader, dataReader) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undefined mockCheckEngine will break build
mockCheckEngine isn’t defined in this file. Use the real CheckEngine to avoid compile issues and still exercise the readSchema error path.
- mockCheckEngine := &mockCheckEngine{}
- engine := NewLookupEngine(mockCheckEngine, schemaReader, dataReader)
+ checkEngine := NewCheckEngine(schemaReader, dataReader)
+ engine := NewLookupEngine(checkEngine, schemaReader, dataReader)Apply the same replacement in all four tests.
Also applies to: 5034-5036, 5065-5067, 5097-5099
🤖 Prompt for AI Agents
internal/engines/lookup_test.go lines 4972-4974 (and similarly 5034-5036,
5065-5067, 5097-5099): the tests instantiate an undefined mockCheckEngine which
will break the build; replace each mockCheckEngine instantiation with a real
CheckEngine (construct it with its required dependencies) so the tests compile
and still exercise the readSchema error path, applying the same replacement in
all four test locations.
Summary by CodeRabbit
Documentation
Chores
Tests
Refactor