Skip to content

Commit fb288e3

Browse files
committed
test-store, tests: Add tests for VID collision prevention
Add a unit test that simulates the ipfs.map() pattern (multiple EntityCache instances sharing one SeqGenerator) and verifies VIDs are sequential. Extend the file_data_sources runner test so that two offchain triggers in the same block each create an entity, exercising the shared VID generator end-to-end.
1 parent d53c3fd commit fb288e3

3 files changed

Lines changed: 55 additions & 3 deletions

File tree

store/test-store/tests/graph/entity_cache.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,37 @@ async fn offchain_trigger_vid_no_collision_with_shared_generator() {
463463
);
464464
}
465465

466+
// Simulate the ipfs.map() pattern: multiple EntityCache instances each create
467+
// an entity using a shared SeqGenerator. VIDs must be unique and sequential.
468+
#[graph::test]
469+
async fn ipfs_map_pattern_vid_uniqueness() {
470+
let block: i32 = 42;
471+
let vid_gen = SeqGenerator::new(block);
472+
473+
let mut all_vids = Vec::new();
474+
for i in 0..5u32 {
475+
let store = Arc::new(MockStore::new(BTreeMap::new()));
476+
let mut cache = EntityCache::new(store, vid_gen.cheap_clone());
477+
let data = entity! { SCHEMA => id: format!("band{i}"), name: format!("Band {i}") };
478+
let key = make_band_key(&format!("band{i}"));
479+
cache.set(key, data, None).await.unwrap();
480+
let result = cache.as_modifications(block, &STOPWATCH).await.unwrap();
481+
let vid = match &result.modifications[0] {
482+
EntityModification::Insert { data, .. } => data.vid(),
483+
_ => panic!("expected Insert"),
484+
};
485+
all_vids.push(vid);
486+
}
487+
488+
for i in 1..all_vids.len() {
489+
assert_eq!(
490+
all_vids[i],
491+
all_vids[i - 1] + 1,
492+
"VIDs should be sequential"
493+
);
494+
}
495+
}
496+
466497
const ACCOUNT_GQL: &str = "
467498
type Account @entity {
468499
id: ID!

tests/runner-tests/file-data-sources/src/mapping.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ export function handleFile(data: Bytes): void {
126126
let contextCommand = context.getString('command');
127127

128128
if (contextCommand == SPAWN_FDS_FROM_OFFCHAIN_HANDLER) {
129+
// Create an entity for THIS file data source too, so that two offchain
130+
// triggers in the same block each write an entity. This exercises the
131+
// shared VID generator: if VIDs collide the DB will reject the insert.
132+
let entity = new FileEntity(dataSource.stringParam());
133+
entity.content = data.toString();
134+
entity.save();
135+
129136
let hash = context.getString('hash');
130137
log.info('Creating file data source from handleFile: {}', [hash]);
131138
dataSource.createWithContext('File', [hash], new DataSourceContext());

tests/tests/runner_tests.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -673,11 +673,25 @@ async fn file_data_sources() {
673673
assert!(datasources.len() == 1);
674674
}
675675

676-
// Create a File data source from a same type of file data source handler
676+
// Create a File data source from a same type of file data source handler.
677+
// Both hash_2 and hash_3 handlers create entities in the same block,
678+
// exercising the shared VID generator: colliding VIDs would cause a DB
679+
// constraint violation.
677680
{
678681
ctx.start_and_sync_to(test_ptr(4)).await;
679682

680-
let content = "EXAMPLE_3";
683+
let query_res = ctx
684+
.query(&format!(
685+
r#"{{ fileEntity(id: "{}") {{ id, content }} }}"#,
686+
hash_2.clone()
687+
))
688+
.await
689+
.unwrap();
690+
assert_json_eq!(
691+
query_res,
692+
Some(object! { fileEntity: object!{ id: hash_2.clone(), content: "EXAMPLE_2" } })
693+
);
694+
681695
let query_res = ctx
682696
.query(&format!(
683697
r#"{{ fileEntity(id: "{}") {{ id, content }} }}"#,
@@ -687,7 +701,7 @@ async fn file_data_sources() {
687701
.unwrap();
688702
assert_json_eq!(
689703
query_res,
690-
Some(object! { fileEntity: object!{ id: hash_3.clone(), content: content } })
704+
Some(object! { fileEntity: object!{ id: hash_3.clone(), content: "EXAMPLE_3" } })
691705
);
692706
}
693707

0 commit comments

Comments
 (0)