-
-
Notifications
You must be signed in to change notification settings - Fork 278
test(gc): add tests for garbage collector handling various scenarios #2652
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
WalkthroughAdds extensive PostgreSQL test coverage for garbage collection and tenant management; CreateTenant now detects pgconn PgError code 23505 for unique-constraint detection; DeleteTenant was refactored to use BeginTx with tx options, run an EXISTS pre-check, execute batched deletions across related tables within a transaction, and improve error mapping and logging. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant TenantWriter
participant DB
Note over TenantWriter,DB: CreateTenant (new handling)
Client->>TenantWriter: CreateTenant(name)
TenantWriter->>DB: INSERT INTO tenants (...)
alt DB returns PgError(code=23505)
DB-->>TenantWriter: PgError(code=23505)
TenantWriter-->>Client: UniqueConstraint error
else DB error (other)
DB-->>TenantWriter: error
TenantWriter-->>Client: mapped error
else success
DB-->>TenantWriter: row created
TenantWriter-->>Client: success
end
sequenceDiagram
participant Client
participant TenantWriter
participant DB
participant Batch
Note over TenantWriter,DB: DeleteTenant (refactored batched delete)
Client->>TenantWriter: DeleteTenant(id)
TenantWriter->>DB: BeginTx(txOptions)
TenantWriter->>DB: SELECT EXISTS(...) WHERE id=$1
alt not exists
DB-->>TenantWriter: false
TenantWriter->>DB: Rollback
TenantWriter-->>Client: NotFound error
else exists
TenantWriter->>Batch: prepare batched DELETEs (relations, attrs, data, tenant)
Batch->>DB: execute batch
alt batch success
DB-->>TenantWriter: OK
TenantWriter->>DB: Commit
DB-->>TenantWriter: Commit OK
TenantWriter-->>Client: success
else batch error
DB-->>TenantWriter: error
TenantWriter->>Batch: Close
TenantWriter->>DB: Rollback
TenantWriter-->>Client: mapped error
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
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
🧹 Nitpick comments (5)
internal/storage/postgres/gc/gc_test.go (3)
388-413: Test name doesn't match test behavior.The test is named "should continue processing other tenants when one tenant fails" but it actually tests a scenario where both tenants are processed successfully - tenant1 has data and tenant2 has no data. Neither tenant "fails" in this test.
Consider renaming to better reflect the actual test behavior:
- It("should continue processing other tenants when one tenant fails", func() { - // Create two tenants - tenant1 := "gc-error-tenant-1" - tenant2 := "gc-error-tenant-2" + It("should handle tenants with varying data states", func() { + // Create two tenants, one with data and one without + tenant1 := "gc-data-tenant-1" + tenant2 := "gc-nodata-tenant-2"
447-484: Test name doesn't match test behavior.Similar to the earlier test, this is named "should handle multiple tenants with mixed success and failure" but all tenants are processed successfully. Tenant3 simply has no data, which is not a failure scenario.
Consider renaming:
- It("should handle multiple tenants with mixed success and failure", func() { + It("should handle multiple tenants with varying data states", func() {
633-634: Consider a stricter assertion.The assertion
transactionCountAfter <= transactionCountBeforeis weak because it would pass even if no transactions were deleted. Since this test specifically aims to verify thatdeleteTransactionsForTenantdeletes old transactions, consider using a stricter assertion:- // After GC, old transactions should be deleted, so count should be less - Expect(transactionCountAfter).Should(BeNumerically("<=", transactionCountBefore)) + // After GC, old transactions should be deleted, so count should be strictly less + Expect(transactionCountAfter).Should(BeNumerically("<", transactionCountBefore))Alternatively, if there's a possibility that no transactions qualify for deletion in some runs, the current assertion is acceptable but the comment should reflect that.
internal/storage/postgres/tenant_writer_test.go (1)
271-385: Tests don't exercise distinct error paths.All six error handling tests (lines 272-385) follow the same pattern: close the database, then call
DeleteTenant. Since the database is closed, all tests will fail at the same point (transaction begin), meaning the specific error paths mentioned in test names (batch execution error, commit error, query row error, batch close error, final commit error) are not actually being exercised.These tests provide basic coverage that errors are handled, but consider consolidating into a single test or documenting that these represent coverage for the general error handling path rather than specific error stages.
internal/storage/postgres/tenant_writer.go (1)
78-79: Comment is slightly misleading.The comment says "Use parameterized query with constant table name for safety" but table names cannot be parameterized in SQL - they're concatenated. The $1 parameter is for the tenantID value. Consider clarifying:
- // Use parameterized query with constant table name for safety + // Table name is a constant; tenant ID is parameterized to prevent SQL injection
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
internal/storage/postgres/gc/gc_test.go(4 hunks)internal/storage/postgres/tenant_writer.go(4 hunks)internal/storage/postgres/tenant_writer_test.go(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/storage/postgres/gc/gc_test.go (3)
internal/storage/postgres/gc/gc.go (1)
NewGC(31-45)internal/storage/postgres/consts.go (1)
TransactionsTable(7-7)pkg/testinstance/postgres.go (1)
PostgresDB(19-89)
internal/storage/postgres/tenant_writer_test.go (3)
internal/storage/postgres/schema_writer.go (1)
NewSchemaWriter(24-29)pkg/database/postgres/postgres.go (1)
Postgres(22-52)internal/storage/postgres/data_writer.go (1)
NewDataWriter(33-38)
⏰ 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). (4)
- GitHub Check: Scan Container for Vulnerabilities
- GitHub Check: Test with Coverage
- GitHub Check: Analyze (go)
- GitHub Check: Test with Coverage
🔇 Additional comments (15)
internal/storage/postgres/gc/gc_test.go (9)
18-18: LGTM!Import added correctly for the new attribute-based test case.
370-386: LGTM!Good test coverage for database connection errors. The updated comment clearly explains the various error stages being tested.
415-424: LGTM!Good edge case coverage for tenants with no transactions.
426-445: LGTM!Good test case verifying that recent data within the GC window is preserved.
486-502: LGTM!Good test for timeout handling with an extremely short timeout to ensure consistent failure.
505-511: LGTM!Good edge case test for GC behavior when no custom tenants exist.
513-546: LGTM!Good test coverage for expired data cleanup, verifying that
deleteRecordsForTenantis invoked for the RelationTuplesTable.
548-583: LGTM!Good test coverage for attribute cleanup, verifying that
deleteRecordsForTenantis called for the AttributesTable.
637-651: LGTM!Good test for empty tenant list scenario with proper resource cleanup of the fresh database instance.
internal/storage/postgres/tenant_writer_test.go (3)
6-18: LGTM!Imports added correctly to support the new test cases for time-based assertions, string operations, schema writing, tuple handling, and ID generation.
62-133: LGTM!Excellent test coverage for
CreateTenant:
- Empty names
- Special characters
- Unicode characters (including emoji)
- Timestamp validation with reasonable tolerance
- Multiple distinct tenants
- Long names
All edge cases are well-structured and assertions are appropriate.
149-246: LGTM!Comprehensive test coverage for
DeleteTenant:
- Not found error handling
- Cascade deletion with associated schema and tuple data
- Tenant recreation after deletion
- Double deletion prevention
- Empty ID handling
Tests align well with the refactored
DeleteTenantimplementation that includes existence checks.internal/storage/postgres/tenant_writer.go (3)
46-51: LGTM!Good improvement using PostgreSQL error code
23505instead of string-based error detection. This is more reliable and follows PostgreSQL best practices for error handling.
87-121: LGTM!Good implementation of batch operations:
- Efficient deletion across multiple related tables in a single batch
- Proper error handling that closes the batch before returning
- Transaction rollback is handled by defer on error paths
119-128: LGTM!Clean implementation with proper error handling for batch close and commit, followed by success logging.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2652 +/- ##
=======================================
Coverage 82.51% 82.52%
=======================================
Files 74 74
Lines 8132 8125 -7
=======================================
- Hits 6710 6705 -5
+ Misses 906 904 -2
Partials 516 516 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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
🧹 Nitpick comments (3)
internal/storage/postgres/tenant_writer_test.go (3)
62-133: CreateTenant edge‑case coverage looks solid; minor timestamp assertion polish possibleThe new tests around empty/special/unicode/long names and
CreatedAtbehavior provide good functional coverage and catch likely regressions. For the timestamp test, you could optionally simplify/readabilize the window check using Gomega’s temporal matchers (e.g.BeTemporally(">=", beforeCreate)/BeTemporally("<=", afterCreate)) instead of manualAdd(±1 * time.Second), but the current approach is correct and reasonably robust.
150-246: DeleteTenant behavior tests are comprehensive; prefer enum constants over raw error stringsThe new tests nicely exercise:
- not‑found behavior (non‑existent ID and empty ID),
- deletion with associated schema data and relation tuples,
- recreation after deletion, and
- double‑delete semantics.
One small consistency issue: the not‑found expectations use hard‑coded strings (
"ERROR_CODE_NOT_FOUND"). Elsewhere in this file you assert againstbase.ErrorCode_... .String(). To keep things type‑safe and resilient to renames, consider switching to:-Expect(err.Error()).Should(Equal("ERROR_CODE_NOT_FOUND")) +Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_NOT_FOUND.String()))for the not‑found cases (Lines 155, 237, 245).
291-471: Error‑handling tests are valuable; avoid hard‑coded PG version and minor redundancyThese error‑path tests (batch exec/close/commit/query‑row failures) add useful coverage by exercising
DeleteTenantagainst closed DB instances. A couple of small improvements:
- Avoid hard‑coding
"14"in separate DBsAll the separate DB setups use:
separateDB := testinstance.PostgresDB("14")while
BeforeEachhonorsPOSTGRES_VERSIONwith a default. If CI or local runs change the target PG version, these tests will still pin to 14 and can diverge in behavior. Consider factoring a helper mirroring the existing logic and reusing it:+func postgresVersion() string { + v := os.Getenv("POSTGRES_VERSION") + if v == "" { + v = "14" + } + return v +}and in the tests:
-separateDB := testinstance.PostgresDB("14") +separateDB := testinstance.PostgresDB(postgresVersion())
- Remove redundant setup in the first batch‑exec test
In
It("should handle batch execution error during table deletion", ...)you create a tenant via the maintenantWriterbefore creating and using the separate DB/tenant. That first creation on the primarydbdoesn’t influence the separate instance and can be safely removed to keep the test focused on the error path in the separate DB.
- Brittle line‑number references in comments
Comments like “(line 101)” / “(line 111)” in this file will drift as
tenant_writer.goevolves. Consider rephrasing them in terms of behavior (“the batch Exec() in the table‑deletion loop”, “the tenant‑deletion Exec path”) rather than specific line numbers.All of these are cleanups; the current tests are functionally correct and provide good error‑path coverage.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/storage/postgres/tenant_writer_test.go(6 hunks)
⏰ 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). (4)
- GitHub Check: Analyze (go)
- GitHub Check: Scan Container for Vulnerabilities
- GitHub Check: Test with Coverage
- GitHub Check: Test with Coverage
🔇 Additional comments (1)
internal/storage/postgres/tenant_writer_test.go (1)
3-19: New imports are all exercised and scoped to testsThe additional imports introduced here are all used in the new test cases and are appropriately test-only concerns; no obvious dead dependencies or misplaced production imports.
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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/storage/postgres/tenant_writer_test.go (1)
281-298: Closing the shareddbinstance may causeAfterEachto fail.This test closes the shared
dbinstance (line 286), butAfterEach(line 37-38) also attempts to close it, which could result in an error. The tests at lines 302-319, 381-398, and 400-417 have the same issue.Consider using a separate database instance for these tests (like the pattern at lines 330, 357, etc.) to avoid double-close issues with the shared
db.Context("CreateTenant Error Handling", func() { It("should handle execution error", func() { ctx := context.Background() - // Create a tenantWriter with a closed database to trigger execution error - closedDB := db.(*PQDatabase.Postgres) - err := closedDB.Close() + // Create a separate database instance and close it to trigger execution error + separateDB := testinstance.PostgresDB("14") + err := separateDB.Close() Expect(err).ShouldNot(HaveOccurred()) - writerWithClosedDB := NewTenantWriter(closedDB) + writerWithClosedDB := NewTenantWriter(separateDB.(*PQDatabase.Postgres)) _, err = writerWithClosedDB.CreateTenant(ctx, "test_id", "test_name")
🧹 Nitpick comments (5)
internal/storage/postgres/tenant_writer_test.go (5)
104-123: Remove redundant distinctness assertions.Lines 119-122 are redundant. Since lines 109, 113, and 117 already assert that each tenant ID equals its unique expected value (
"multi_tenant_1","multi_tenant_2","multi_tenant_3"), their distinctness is already proven.Expect(tenant3.Id).Should(Equal("multi_tenant_3")) - - // Verify all tenants are distinct - Expect(tenant1.Id).ShouldNot(Equal(tenant2.Id)) - Expect(tenant2.Id).ShouldNot(Equal(tenant3.Id)) - Expect(tenant1.Id).ShouldNot(Equal(tenant3.Id)) })
91-102: Consider using Gomega'sBeTemporallymatcher for cleaner timestamp assertions.The current approach works, but Gomega provides a more expressive matcher for time comparisons.
Expect(err).ShouldNot(HaveOccurred()) Expect(tenant.CreatedAt).ShouldNot(BeNil()) - Expect(tenant.CreatedAt.AsTime().After(beforeCreate.Add(-1 * time.Second))).Should(BeTrue()) - Expect(tenant.CreatedAt.AsTime().Before(afterCreate.Add(1 * time.Second))).Should(BeTrue()) + Expect(tenant.CreatedAt.AsTime()).Should(BeTemporally(">=", beforeCreate.Add(-1*time.Second))) + Expect(tenant.CreatedAt.AsTime()).Should(BeTemporally("<=", afterCreate.Add(1*time.Second)))
158-178: Consider verifying associated data is actually deleted.This test confirms that
DeleteTenantsucceeds when schema data exists, but doesn't verify that the schema data was actually removed. Consider adding a verification step (e.g., attempting to read the schema after deletion) to ensure the cascade delete works correctly.
321-351: Consider removing hard-coded line number references in comments.Comments like "This will test the br.Exec() error path in the loop (line 101)" reference specific line numbers in
tenant_writer.gothat may become stale as the implementation evolves. Consider describing the error path by its logical purpose instead.- // Close the database to trigger batch execution error - // This will test the br.Exec() error path in the loop (line 101) + // Close the database to trigger batch execution error + // This tests the batch execution error path during table deletion loop
381-417: Tests "commit error after batch execution" and "query row error" appear to test the same error path.Both tests close the shared
dbinstance and then callDeleteTenant. Since the database is already closed, both will fail at the same point (transaction begin), not at their intended error paths (commit or query row). These tests are effectively duplicates.Consider either:
- Removing one of these redundant tests, or
- Using different mechanisms (e.g., mocks, fault injection) to trigger distinct error conditions at the specific paths.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/storage/postgres/tenant_writer_test.go(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/storage/postgres/tenant_writer_test.go (5)
internal/storage/postgres/schema_writer.go (1)
NewSchemaWriter(24-29)internal/storage/postgres/data_writer.go (1)
NewDataWriter(33-38)pkg/testinstance/postgres.go (1)
PostgresDB(19-89)internal/storage/postgres/tenant_writer.go (1)
NewTenantWriter(29-34)pkg/pb/base/v1/errors.pb.go (3)
ErrorCode_ERROR_CODE_EXECUTION(87-87)ErrorCode_ERROR_CODE_SQL_BUILDER(85-85)ErrorCode_ERROR_CODE_SCAN(88-88)
⏰ 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). (4)
- GitHub Check: Scan Container for Vulnerabilities
- GitHub Check: Analyze (go)
- GitHub Check: Test with Coverage
- GitHub Check: Test with Coverage
🔇 Additional comments (5)
internal/storage/postgres/tenant_writer_test.go (5)
1-19: LGTM!Imports are well-organized and all newly added imports (
strings,time,storage,tuple,xid) are utilized in the new test cases.
270-276: LGTM!Documents the current behavior for empty ID handling. The
ERROR_CODE_NOT_FOUNDresponse is reasonable since an empty ID won't match any existing tenant.
201-228: Comprehensive data cleanup test is valuable.This test verifies that
DeleteTenantsucceeds when multiple data types (schema, tuples) are associated with the tenant. The test coverage for the full batch operation flow is good.As mentioned for the schema-only test, consider adding assertions to verify that the associated data (schema definitions, relation tuples) is actually removed after deletion, not just that the operation succeeds without error.
230-251: LGTM!Good test for verifying tenant recreation after deletion. This confirms that deletion properly cleans up the unique constraint, allowing the same ID to be reused.
475-501: Test setup correctly uses separate DB instance.This test properly creates an isolated database instance to test the commit error path, avoiding the double-close issue present in earlier error tests.
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
🧹 Nitpick comments (4)
internal/storage/postgres/tenant_writer_test.go (4)
145-151: Prefer using the base error code constant instead of hard‑coded"ERROR_CODE_NOT_FOUND"These tests currently assert on a raw string literal:
- Line 150: non‑existent tenant
- Line 270: second deletion
- Line 278: empty ID
To keep tests aligned with the proto definition and avoid drift if the string ever changes, you can assert using the enum’s
.String()like you do for the unique‑constraint case:- Expect(err.Error()).Should(Equal("ERROR_CODE_NOT_FOUND")) + Expect(err.Error()).Should(Equal(base.ErrorCode_ERROR_CODE_NOT_FOUND.String()))Apply the same change to all three occurrences.
Also applies to: 256-271, 273-279
183-231: Consider asserting DB cleanup for tuples/all‑data cases, similar to the schema testFor the “associated relation tuples” and “all associated data types” tests you only assert that
DeleteTenantreturns no error. A regression where some tables aren’t fully cleaned up but still returnnilwould slip through.You already directly check the schema table count in the schema test; mirroring that pattern for tuples and other data (using the appropriate table constants) would tighten these tests and validate the full batch‑deletion behavior.
324-354: Remove redundant tenant creation against the maintenantWriterIn
It("should handle batch execution error during table deletion")you:
- First create a tenant via the main
tenantWriter(default DB).- Then create a separate DB and a separate tenant there.
- Trigger the error path using
writerWithClosedDBbuilt from the separate DB.The initial creation on the main DB isn’t used by this test and can be dropped to reduce noise and make it clearer which database you’re exercising:
- // Create a tenant first - tenantID := "batch-exec-error-tenant" - _, err := tenantWriter.CreateTenant(ctx, tenantID, "Batch Exec Error Tenant") - Expect(err).ShouldNot(HaveOccurred()) - - // Create a separate database instance for this test - separateDB := testinstance.PostgresDB("14") - separateWriter := NewTenantWriter(separateDB.(*PQDatabase.Postgres)) - - // Create tenant in separate DB - _, err = separateWriter.CreateTenant(ctx, tenantID, "Batch Exec Error Tenant") + // Create a tenant only in the separate database whose deletion path we want to exercise + tenantID := "batch-exec-error-tenant" + separateDB := testinstance.PostgresDB("14") + separateWriter := NewTenantWriter(separateDB.(*PQDatabase.Postgres)) + + _, err := separateWriter.CreateTenant(ctx, tenantID, "Batch Exec Error Tenant") Expect(err).ShouldNot(HaveOccurred())
287-301: Error‑handling tests are thorough; a couple of minor maintainability nitsThe matrix of execution/tx/batch/connection/commit error paths is very well covered. Two small nits you might consider:
Avoid line‑number references in comments
The commit‑error test comment currently mentions a specific source line:
// This will test the tx.Commit() error path (line 123)This will go stale quickly. You can keep the intent without tying it to a line number:
// This will test the tx.Commit() error path (line 123)
// This will exercise the tx.Commit() error path in DeleteTenant
Reduce duplication in DB setup and error assertions (optional)
testinstance.PostgresDB("14")is repeated across many tests; if you ever change the default version logic, these can easily drift from theBeforeEachbehavior. A small helper (e.g.newTestPostgresDB()that respectsPOSTGRES_VERSIONwith a default) would centralize that choice.- The
Expect(err.Error()).Should(Or(Equal(...)))block is duplicated across all error‑handling tests; a helper likeexpectTenantWriterError(err)would make it easier to adjust the allowed codes in one place.Given this is test code, these are purely cleanliness/DRY improvements, not correctness issues.
Also applies to: 308-322, 324-486
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/storage/postgres/tenant_writer_test.go(9 hunks)
⏰ 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 (2)
internal/storage/postgres/tenant_writer_test.go (2)
6-19: New imports are all used correctly
strings,time,storage,tuple, andxidare all exercised in the new tests; the import block is consistent and there’s no dead import here.
62-128: CreateTenant edge‑case coverage looks solidNice breadth here: empty name, special chars, unicode, timestamp assertions, multi‑tenant creation, and long names give good confidence in the writer’s behavior. Assuming the underlying column length can accommodate
longName, this all looks correct and non‑flaky for typical CI runtimes.
Summary by CodeRabbit
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.