Skip to content

Comments

test(drive): add error path tests for document CRUD and cache lifecycle#3129

Open
thepastaclaw wants to merge 3 commits intodashpay:v3.1-devfrom
thepastaclaw:test/rs-drive-document-crud-error-paths
Open

test(drive): add error path tests for document CRUD and cache lifecycle#3129
thepastaclaw wants to merge 3 commits intodashpay:v3.1-devfrom
thepastaclaw:test/rs-drive-document-crud-error-paths

Conversation

@thepastaclaw
Copy link
Contributor

@thepastaclaw thepastaclaw commented Feb 20, 2026

Summary

Add error path tests for core document CRUD operations in rs-drive, and add comprehensive lifecycle tests for DataContractCache.

Tracker: thepastaclaw/tracker#17
Parent audit: thepastaclaw/tracker#11

What's Changed

Error Path Tests for Document Operations

C6 — Duplicate Insert: test_add_dashpay_document_duplicate_insert_returns_error

  • Inserts a document, then attempts to insert the same document again
  • Asserts the error is specifically DriveError::CorruptedDocumentAlreadyExists

C7 — Update Nonexistent: test_update_nonexistent_document_returns_error

  • Creates a document in memory (never inserted into Drive)
  • Attempts to update it and asserts the operation fails
  • Accepts either DriveError::UpdatingDocumentThatDoesNotExist or GroveDB::PathKeyNotFound

C8 — Delete Nonexistent: test_delete_nonexistent_document_returns_error

  • Generates a random document ID that was never inserted
  • Attempts to delete it and asserts the operation fails
  • Accepts either DriveError::DeletingDocumentThatDoesNotExist or GroveDB::PathKeyNotFound

Cache Lifecycle Tests (C10)

Added test submodules for DataContractCache covering the full lifecycle:

  • insert — Insert into global cache, insert into block cache, verify retrieval
  • remove — Insert then remove, verify entry is gone from both caches
  • merge_and_clear_block_cache — Insert into block cache, merge to global, verify promotion and block cache clearance
  • clear — Insert into both caches, clear all, verify everything is gone

Deferred

  • I7 (Concurrency tests): Deferred — would require threading infrastructure not currently present in the test suite. The DataContractCache uses moka::sync::Cache internally which is thread-safe, but adding proper concurrent tests is out of scope for this PR.

Test Results

All new tests pass:

  • cargo test -p drive -- test_add_dashpay_document_duplicate
  • cargo test -p drive -- test_update_nonexistent
  • cargo test -p drive -- test_delete_nonexistent
  • cargo test -p drive -- cache::data_contract::tests ✅ (10 tests total)

Summary by CodeRabbit

  • Tests
    • Expanded test coverage for data contract caching and document operations with new error handling scenarios, including duplicate insertion, deletion of non-existent records, and cache management behaviors.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

Warning

Rate limit exceeded

@thepastaclaw has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

These changes expand test coverage for document operations and cache functionality. New tests verify error handling for non-existent and duplicate document operations, while additional cache tests validate global vs. block cache behavior and cache clearing scenarios.

Changes

Cohort / File(s) Summary
DataContractCache Tests
packages/rs-drive/src/cache/data_contract.rs
Expanded test coverage with new modules for insert, remove, merge_and_clear_block_cache, and clear operations, verifying global vs. block cache behavior and cache clearing scenarios.
Document Operation Error Tests
packages/rs-drive/src/drive/document/delete/mod.rs, packages/rs-drive/src/drive/document/insert/mod.rs, packages/rs-drive/src/drive/document/update/mod.rs
Added error handling tests: verify deletion and updates of non-existent documents return appropriate errors, and duplicate document insertion returns CorruptedDocumentAlreadyExists error.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 With whiskers twitching, tests take flight,
Catching errors left and right,
Non-existent docs we track,
Cache behavior, none we lack,
Quality code, our pure delight! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'test(drive): add error path tests for document CRUD and cache lifecycle' accurately summarizes the main changes, covering the addition of error-path tests for document operations (insert, update, delete) and DataContractCache lifecycle tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
packages/rs-drive/src/drive/document/insert/mod.rs (1)

158-226: LGTM — the test correctly targets the C6 error path and the assert!(matches!(...)) assertion on the specific DriveError::CorruptedDocumentAlreadyExists(_) variant is idiomatic and well-scoped.

One optional cleanup: Lines 159–198 are nearly verbatim copies of test_add_dashpay_documents_no_transaction's setup and first insert. Consider extracting the shared setup into a small helper (returning (Drive, DataContract, DocumentType, Document, [u8; 32], &PlatformVersion)) to reduce the copy-paste surface in this test module.

♻️ Sketch of a shared setup helper
+    fn setup_dashpay_contact_request_first_version(
+        label: &str,
+    ) -> (
+        crate::drive::Drive,
+        dpp::data_contract::DataContract,
+        dpp::version::PlatformVersion,
+    ) {
+        let (drive, dashpay) = setup_dashpay(label, true);
+        let platform_version = PlatformVersion::first();
+        (drive, dashpay, *platform_version)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-drive/src/drive/document/insert/mod.rs` around lines 158 - 226,
The test test_add_dashpay_document_duplicate_insert_returns_error duplicates
setup logic from test_add_dashpay_documents_no_transaction; extract the common
setup into a small helper (e.g., fn setup_dashpay_test_context()) that returns
the Drive, DataContract (dashpay), DocumentType, prepared Document
(dashpay_cr_document), owner id (random_owner_id) and PlatformVersion; update
both tests to call this helper and use the returned values when calling
add_document_for_contract and asserting the duplicate error (keep the existing
assertion on DriveError::CorruptedDocumentAlreadyExists).
packages/rs-drive/src/drive/document/update/mod.rs (1)

254-299: LGTM — well-structured error-path test.

A few observations:

  • Correctly uses mutable_contact_requests: true so the error is about the document not existing rather than the type being immutable — the same contactRequest fixture is used by test_modify_dashpay_contact_request with the non-mutable contract, so they exercise complementary paths cleanly.
  • The dual-error assertion (UpdatingDocumentThatDoesNotExist OR PathKeyNotFound) is an acceptable safety net for an error-path test, consistent with the pattern used for the delete nonexistent test described in the PR.
  • The "update-nonexistent" prefix string passed to setup_dashpay is silently discarded (_prefix). Since all other setup_dashpay call-sites already pass "", consider using "" here too for consistency, though this has zero functional impact.
🧹 Nit: use consistent empty prefix
-        let (drive, contract) = setup_dashpay("update-nonexistent", true);
+        let (drive, contract) = setup_dashpay("", true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-drive/src/drive/document/update/mod.rs` around lines 254 - 299,
The test test_update_nonexistent_document_returns_error passes a non-empty
prefix string ("update-nonexistent") to setup_dashpay even though that parameter
is ignored by the function; change the call in
test_update_nonexistent_document_returns_error to pass "" for consistency with
other tests (i.e., replace setup_dashpay("update-nonexistent", true) with
setup_dashpay("", true)) while leaving all other logic, assertions, and
references to document, contract, and update_document_for_contract unchanged.
packages/rs-drive/src/cache/data_contract.rs (2)

186-198: test_insert_into_block_cache doesn't verify cache isolation

get(contract_id, true) falls back to global_cache when the block cache misses, so this assertion would pass even if insert(..., true) accidentally routed to global_cache. A direct global_cache.get absence check removes that ambiguity. The same gap exists symmetrically in test_insert_into_global_cache (no assertion that block_cache is empty).

🔧 Proposed fix
         let from_cache = data_contract_cache.get(contract_id, true);
         assert_eq!(from_cache, Some(fetch_info));
+
+        // Verify the entry went to block_cache and not global_cache
+        assert!(data_contract_cache.global_cache.get(&contract_id).is_none());
     }
 }

And symmetrically for test_insert_into_global_cache:

         let from_cache = data_contract_cache.get(contract_id, false);
         assert_eq!(from_cache, Some(fetch_info));
+
+        // Verify the entry went to global_cache and not block_cache
+        assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-drive/src/cache/data_contract.rs` around lines 186 - 198, The
test test_insert_into_block_cache currently can pass due to fallback to
global_cache; update the test (and symmetrically test_insert_into_global_cache)
to assert cache isolation: after calling data_contract_cache.insert(fetch_info,
true) assert that data_contract_cache.global_cache.get(&contract_id) is None (or
equivalent) to ensure the entry was placed only in block_cache, and in
test_insert_into_global_cache assert
data_contract_cache.block_cache.get(&contract_id) is None after inserting with
false; keep existing assertions that get(contract_id, true/false) returns the
correct fetch_info.

237-267: Missing test: block cache entry overwrites stale global cache entry on merge

Both test_merge_moves_block_items_to_global_cache and test_merge_clears_block_cache start with an empty global cache. The more important invariant — that merge_and_clear_block_cache lets a newer block-cache version win over a stale global-cache version — has no coverage. This is the primary production scenario (a block update supersedes the cached global state).

🧪 Suggested additional test
#[test]
fn test_merge_block_cache_overwrites_global_cache() {
    use dpp::data_contract::accessors::v0::DataContractV0Setters;

    let data_contract_cache = DataContractCache::new(10, 10);
    let protocol_version = PlatformVersion::latest().protocol_version;

    // Insert a "stale" version into global cache
    let fetch_info_global =
        Arc::new(DataContractFetchInfo::dpns_contract_fixture(protocol_version));
    let contract_id = fetch_info_global.contract.id().to_buffer();
    data_contract_cache.insert(Arc::clone(&fetch_info_global), false);

    // Insert an updated version into block cache
    let mut fetch_info_block = DataContractFetchInfo::dpns_contract_fixture(protocol_version);
    fetch_info_block.contract.increment_version();
    let fetch_info_block = Arc::new(fetch_info_block);
    data_contract_cache.insert(Arc::clone(&fetch_info_block), true);

    data_contract_cache.merge_and_clear_block_cache();

    // Global cache should now hold the block cache version
    let from_global = data_contract_cache
        .global_cache
        .get(&contract_id)
        .expect("should be present in global cache");
    assert_eq!(from_global, fetch_info_block);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-drive/src/cache/data_contract.rs` around lines 237 - 267, The
tests miss verifying that merge_and_clear_block_cache lets a newer block-cache
entry overwrite a stale global-cache entry; add a test (e.g.,
test_merge_block_cache_overwrites_global_cache) that 1) creates a
DataContractCache, 2) inserts a "stale" DataContractFetchInfo into the global
cache via DataContractCache::insert(..., false), 3) creates a newer block
version by calling increment_version on a DataContract (use
DataContractV0Setters) and inserts it into the block cache via insert(...,
true), 4) calls DataContractCache::merge_and_clear_block_cache(), and 5) asserts
that DataContractCache::global_cache.get(contract_id) returns the block-cache
version (compare to the Arc of the updated fetch info). Use the existing symbols
DataContractCache, insert, merge_and_clear_block_cache, and
DataContractV0Setters to locate code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/rs-drive/src/cache/data_contract.rs`:
- Around line 186-198: The test test_insert_into_block_cache currently can pass
due to fallback to global_cache; update the test (and symmetrically
test_insert_into_global_cache) to assert cache isolation: after calling
data_contract_cache.insert(fetch_info, true) assert that
data_contract_cache.global_cache.get(&contract_id) is None (or equivalent) to
ensure the entry was placed only in block_cache, and in
test_insert_into_global_cache assert
data_contract_cache.block_cache.get(&contract_id) is None after inserting with
false; keep existing assertions that get(contract_id, true/false) returns the
correct fetch_info.
- Around line 237-267: The tests miss verifying that merge_and_clear_block_cache
lets a newer block-cache entry overwrite a stale global-cache entry; add a test
(e.g., test_merge_block_cache_overwrites_global_cache) that 1) creates a
DataContractCache, 2) inserts a "stale" DataContractFetchInfo into the global
cache via DataContractCache::insert(..., false), 3) creates a newer block
version by calling increment_version on a DataContract (use
DataContractV0Setters) and inserts it into the block cache via insert(...,
true), 4) calls DataContractCache::merge_and_clear_block_cache(), and 5) asserts
that DataContractCache::global_cache.get(contract_id) returns the block-cache
version (compare to the Arc of the updated fetch info). Use the existing symbols
DataContractCache, insert, merge_and_clear_block_cache, and
DataContractV0Setters to locate code.

In `@packages/rs-drive/src/drive/document/insert/mod.rs`:
- Around line 158-226: The test
test_add_dashpay_document_duplicate_insert_returns_error duplicates setup logic
from test_add_dashpay_documents_no_transaction; extract the common setup into a
small helper (e.g., fn setup_dashpay_test_context()) that returns the Drive,
DataContract (dashpay), DocumentType, prepared Document (dashpay_cr_document),
owner id (random_owner_id) and PlatformVersion; update both tests to call this
helper and use the returned values when calling add_document_for_contract and
asserting the duplicate error (keep the existing assertion on
DriveError::CorruptedDocumentAlreadyExists).

In `@packages/rs-drive/src/drive/document/update/mod.rs`:
- Around line 254-299: The test test_update_nonexistent_document_returns_error
passes a non-empty prefix string ("update-nonexistent") to setup_dashpay even
though that parameter is ignored by the function; change the call in
test_update_nonexistent_document_returns_error to pass "" for consistency with
other tests (i.e., replace setup_dashpay("update-nonexistent", true) with
setup_dashpay("", true)) while leaving all other logic, assertions, and
references to document, contract, and update_document_for_contract unchanged.

@thepastaclaw
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

✅ Actions performed

Review triggered.

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


let mut filter =
CoreBloomFilter::from_bytes(vec![0; 256], 3, 0, BloomFlags::None).unwrap();
let mut filter = CoreBloomFilter::from_bytes(vec![0; 256], 3, 0, BloomFlags::None).unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

please remove these unrelated fmt changes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done — reverted the bloom.rs fmt changes from this PR. They're a pre-existing cargo fmt non-conformance; if we want to fix them, it should be a separate PR.

@thepastaclaw thepastaclaw force-pushed the test/rs-drive-document-crud-error-paths branch from 0e730e3 to b0ed8ea Compare February 21, 2026 05:38
@thepastaclaw
Copy link
Contributor Author

Addressed CodeRabbit review feedback:

Implemented:

  • ✅ Cache isolation assertions in test_insert_into_block_cache and test_insert_into_global_cache — now verify entries don't leak between caches
  • ✅ New test test_merge_block_cache_overwrites_stale_global_cache — verifies merge correctly replaces stale global entries with newer block versions
  • ✅ Changed setup_dashpay("update-nonexistent", true)setup_dashpay("", true) for consistency (prefix is ignored)
  • ✅ Reverted unrelated bloom.rs fmt changes per Pasta's review

Skipped (nitpick, low value):

  • Extract shared setup helper in insert/mod.rs — duplication is limited and a helper adds complexity for minimal gain in test code

@thepastaclaw thepastaclaw force-pushed the test/rs-drive-document-crud-error-paths branch from b0ed8ea to 96702d8 Compare February 21, 2026 18:02
@thepastaclaw thepastaclaw force-pushed the test/rs-drive-document-crud-error-paths branch from 96702d8 to 940aedb Compare February 21, 2026 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants