#![allow(clippy::expect_used, clippy::unwrap_used)]
mod conformance;
use graph_storage::infra::fake_store::FakeGraphStore;
use graph_storage_sdk::models::ProjectionRequest;
use uuid::Uuid;
fn store() -> FakeGraphStore {
FakeGraphStore::new()
}
#[tokio::test]
async fn a_failed_batch_commits_nothing() {
conformance::batch_atomicity(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn source_generations_are_fenced_monotonically() {
conformance::generation_fencing(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_node_never_outlives_its_incident_edges() {
conformance::no_orphan_edges(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_fresh_tenant_reports_a_usable_revision() {
conformance::a_fresh_tenant_reports_a_usable_revision(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn materializing_a_phantom_revalidates_its_edges() {
conformance::materializing_a_phantom_revalidates_its_edges(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn an_edge_type_refuses_an_endpoint_it_does_not_admit() {
conformance::endpoint_constraints_are_enforced(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_recorded_idempotency_key_replays() {
conformance::idempotency(&store(), Uuid::now_v7()).await;
conformance::a_keyless_retry_is_a_new_request(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn an_identical_batch_converges_without_moving_the_revision() {
conformance::convergent_replay(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn an_unchanged_re_ingest_embeds_nothing() {
conformance::an_unchanged_re_ingest_embeds_nothing(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn colliding_node_keys_stay_inside_their_tenants() {
conformance::tenant_isolation(&store(), Uuid::now_v7(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn tombstoned_rows_are_absent_from_every_read_path() {
conformance::tombstones_are_invisible(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_denied_row_reads_exactly_like_an_absent_one() {
conformance::denied_is_indistinguishable_from_absent(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_denying_scope_ranks_nothing() {
conformance::search_is_scoped(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_document_is_retrieved_by_its_own_text() {
conformance::a_document_is_retrieved_by_its_own_text(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_declared_path_reaches_the_vector() {
conformance::a_declared_path_reaches_the_vector(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_skipped_re_ingest_preserves_the_vector() {
conformance::a_skipped_re_ingest_preserves_the_vector(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_stale_vector_stops_ranking_but_the_node_stays() {
conformance::a_stale_vector_stops_ranking_but_the_node_stays(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn only_the_active_epoch_ranks() {
conformance::only_the_active_epoch_ranks(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn one_snapshot_spans_every_arm_of_one_read() {
use graph_storage_sdk::plugin_api::GraphStoreV1;
use toolkit_security::AccessScope;
let store = store();
let tenant = Uuid::now_v7();
let scope = AccessScope::for_tenant(tenant);
let ctx = conformance::ctx(tenant, &scope, None);
store
.register_types(&ctx, conformance::ontology_batch())
.await
.expect("ontology registers");
conformance::ingest_batch(
&store,
&ctx,
conformance::batch(vec![conformance::node("snap-1", "before")], Vec::new()),
)
.await
.expect("the batch commits");
let snapshot = store.begin_read(&ctx).await.expect("snapshot opens");
conformance::ingest_batch(
&store,
&ctx,
conformance::batch(vec![conformance::node("snap-2", "after")], Vec::new()),
)
.await
.expect("the concurrent batch commits");
let under = conformance::ctx(tenant, &scope, Some(&snapshot));
let page = store
.project_table(&under, ProjectionRequest::default())
.await
.expect("projection succeeds");
assert!(
page.items.iter().all(|row| row.node_key != "snap-2"),
"the snapshot must not see a row committed after it opened: {:?}",
page.items
);
let resolved = store
.resolve_node_ids(&under, &["snap-2".to_owned()])
.await
.expect("resolution succeeds");
assert!(
resolved.is_empty(),
"a second arm of the same read observes the same state"
);
store.end_read(snapshot).await.expect("snapshot closes");
}
#[tokio::test]
async fn the_envelope_records_the_subject_of_each_verb() {
conformance::the_envelope_records_the_subject_of_each_verb(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_projection_row_carries_the_envelope() {
conformance::a_projection_row_carries_the_envelope(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_declared_payload_path_filters_and_orders_the_projection() {
conformance::a_declared_payload_path_filters_and_orders_the_projection(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn an_undeclared_payload_path_is_refused_naming_the_alternatives() {
conformance::an_undeclared_payload_path_is_refused_naming_the_alternatives(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn an_index_path_onto_a_non_scalar_is_refused_at_registration() {
conformance::an_index_path_onto_a_non_scalar_is_refused_at_registration(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn a_deeper_chain_registers_and_its_ancestor_admits_the_leaf() {
conformance::a_deeper_chain_registers_and_its_ancestor_admits_the_leaf(
&store().with_max_chain_depth(8),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn a_backward_compatible_change_updates_the_type_in_place() {
conformance::a_backward_compatible_change_updates_the_type_in_place(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn an_incompatible_change_is_refused_with_its_location() {
conformance::an_incompatible_change_is_refused_with_its_location(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn a_changed_schema_is_still_a_conflict_by_default() {
conformance::a_changed_schema_is_still_a_conflict_by_default(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_dry_run_reports_every_verdict_and_writes_nothing() {
conformance::a_dry_run_reports_every_verdict_and_writes_nothing(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_change_the_schemas_cannot_prove_is_admitted_when_the_rows_fit() {
conformance::a_change_the_schemas_cannot_prove_is_admitted_when_the_rows_fit(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn a_change_the_stored_rows_contradict_is_refused_naming_them() {
conformance::a_change_the_stored_rows_contradict_is_refused_naming_them(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn an_accepted_type_update_advances_the_graph_revision() {
conformance::an_accepted_type_update_advances_the_graph_revision(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn a_new_index_path_becomes_filterable_without_recreating_the_type() {
conformance::a_new_index_path_becomes_filterable_without_recreating_the_type(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn a_migration_moves_the_data_with_the_type() {
conformance::a_migration_moves_the_data_with_the_type(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_migration_that_leaves_rows_invalid_is_refused_naming_them() {
conformance::a_migration_that_leaves_rows_invalid_is_refused_naming_them(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn a_migration_without_a_schema_change_is_refused() {
conformance::a_migration_without_a_schema_change_is_refused(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_migration_stamps_its_writer_and_moves_the_version() {
conformance::a_migration_stamps_its_writer_and_moves_the_version(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn a_source_namespace_is_claimed_by_its_first_writer() {
conformance::a_source_namespace_is_claimed_by_its_first_writer(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn writing_under_another_producers_namespace_is_forbidden() {
conformance::writing_under_another_producers_namespace_is_forbidden(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn a_transfer_moves_the_namespace_and_records_who_moved_it() {
conformance::a_transfer_moves_the_namespace_and_records_who_moved_it(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn an_owned_nodes_source_field_claims_no_namespace() {
conformance::an_owned_nodes_source_field_claims_no_namespace(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn readiness_reports_every_capability_and_only_some_block_service() {
conformance::readiness_reports_every_capability_and_only_some_block_service(&store()).await;
}
#[tokio::test]
async fn scope_replacement_removes_what_the_batch_no_longer_names() {
conformance::scope_replacement_removes_what_the_batch_no_longer_names(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn a_store_without_labels_refuses_every_label_call() {
conformance::a_store_without_labels_refuses_every_label_call(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn an_edge_cannot_name_a_tombstoned_endpoint() {
conformance::an_edge_cannot_name_a_tombstoned_endpoint(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn node_types_answers_the_live_nodes_it_is_asked_about() {
conformance::node_types_answers_the_live_nodes_it_is_asked_about(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn an_edge_only_scope_drops_the_edges_it_stops_declaring() {
conformance::an_edge_only_scope_drops_the_edges_it_stops_declaring(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn scope_replacement_removes_an_edge_whose_endpoints_remain() {
conformance::scope_replacement_removes_an_edge_whose_endpoints_remain(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn an_edge_is_not_taken_from_the_scope_that_declared_it() {
conformance::an_edge_is_not_taken_from_the_scope_that_declared_it(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn one_replacement_does_not_take_another_scopes_edges() {
conformance::one_replacement_does_not_take_another_scopes_edges(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn scope_replacement_preserves_analysis_edges_and_their_endpoints() {
conformance::scope_replacement_preserves_analysis_edges_and_their_endpoints(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn two_replacements_of_one_scope_serialize() {
conformance::two_replacements_of_one_scope_serialize(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn every_update_of_a_node_advances_its_version() {
conformance::every_update_of_a_node_advances_its_version(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn two_writers_with_one_expected_version_do_not_both_win() {
conformance::two_writers_with_one_expected_version_do_not_both_win(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn two_type_updates_do_not_share_one_revision() {
conformance::two_type_updates_do_not_share_one_revision(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_delete_racing_an_upsert_leaves_no_rewritten_tombstone() {
conformance::a_delete_racing_an_upsert_leaves_no_rewritten_tombstone(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_replacement_does_not_delete_what_an_ingest_moved_out_of_its_scope() {
conformance::a_replacement_does_not_delete_what_an_ingest_moved_out_of_its_scope(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn two_batches_naming_one_new_endpoint_both_land() {
conformance::two_batches_naming_one_new_endpoint_both_land(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_node_delete_racing_its_edge_delete_counts_every_edge_once() {
conformance::a_node_delete_racing_its_edge_delete_counts_every_edge_once(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn two_deletes_of_one_node_tombstone_it_once() {
conformance::two_deletes_of_one_node_tombstone_it_once(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_deleted_edge_is_revived_by_the_next_upsert() {
conformance::a_deleted_edge_is_revived_by_the_next_upsert(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn every_committed_mutation_gets_its_own_revision() {
conformance::every_committed_mutation_gets_its_own_revision(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn both_node_families_and_both_edge_families_round_trip() {
conformance::both_node_families_and_both_edge_families_round_trip(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn an_edge_read_carries_the_envelope() {
conformance::an_edge_read_carries_the_envelope(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn an_edge_whose_endpoint_is_hidden_is_not_readable() {
conformance::an_edge_whose_endpoint_is_hidden_is_not_readable(
&store(),
Uuid::now_v7(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn no_read_surface_answers_with_another_tenants_rows() {
conformance::no_read_surface_answers_with_another_tenants_rows(
&store(),
Uuid::now_v7(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn an_edge_type_evolves_over_its_own_rows() {
conformance::an_edge_type_evolves_over_its_own_rows(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_same_key_ingest_may_not_change_the_type() {
conformance::a_same_key_ingest_may_not_change_the_type(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn per_item_outcomes_follow_the_batch_order() {
conformance::per_item_outcomes_follow_the_batch_order(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_scope_and_an_idempotency_key_belong_to_their_producer() {
conformance::a_scope_and_an_idempotency_key_belong_to_their_producer(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn a_type_pattern_narrows_search_and_a_hop() {
conformance::a_type_pattern_narrows_search_and_a_hop(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn hybrid_search_fuses_both_arms() {
conformance::hybrid_search_fuses_both_arms(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn deleting_an_already_tombstoned_row_is_a_no_op() {
conformance::deleting_an_already_tombstoned_row_is_a_no_op(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn the_type_catalogue_pages_through_its_own_cursor() {
conformance::the_type_catalogue_pages_through_its_own_cursor(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_deleted_conclusion_stops_pinning_its_endpoint() {
conformance::a_deleted_conclusion_stops_pinning_its_endpoint(&store(), Uuid::now_v7()).await;
}
#[tokio::test]
async fn a_batch_that_names_one_type_twice_is_refused() {
conformance::a_batch_that_names_one_type_twice_is_refused(&store(), Uuid::now_v7()).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn two_scopes_racing_to_claim_an_unowned_edge_leave_it_with_one() {
conformance::two_scopes_racing_to_claim_an_unowned_edge_leave_it_with_one(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn an_edge_does_not_follow_a_node_ingested_under_a_new_key() {
conformance::an_edge_does_not_follow_a_node_ingested_under_a_new_key(&store(), Uuid::now_v7())
.await;
}
#[tokio::test]
async fn an_expected_version_on_an_absent_key_is_a_conflict_unless_it_is_zero() {
conformance::an_expected_version_on_an_absent_key_is_a_conflict_unless_it_is_zero(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn a_batch_with_one_conflicting_type_registers_none_and_names_it() {
conformance::a_batch_with_one_conflicting_type_registers_none_and_names_it(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test]
async fn a_replacement_leaves_alone_what_does_not_carry_its_attribute() {
conformance::a_replacement_leaves_alone_what_does_not_carry_its_attribute(
&store(),
Uuid::now_v7(),
)
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn two_creators_with_expected_version_zero_do_not_both_win() {
conformance::two_creators_with_expected_version_zero_do_not_both_win(
std::sync::Arc::new(store())
as std::sync::Arc<dyn graph_storage_sdk::plugin_api::GraphStoreV1>,
Uuid::now_v7(),
)
.await;
}