Skip to main content
Glama
change_set_approval.rs50.1 kB
use std::collections::{ HashMap, HashSet, }; use dal::{ Component, ComponentType, DalContext, SchemaVariant, Ulid, action::Action, approval_requirement::{ ApprovalRequirement, ApprovalRequirementApprover, ApprovalRequirementDefinition, }, change_set::approval::ChangeSetApproval, diagram::view::View, }; use dal_test::{ Result, eyre, helpers::{ create_component_for_default_schema_name, create_component_for_default_schema_name_in_default_view, create_schema, }, prelude::ChangeSetTestHelpers, sdf_test, }; use indoc::indoc; use permissions::{ ObjectType, Relation, RelationBuilder, }; use pretty_assertions_sorted::assert_eq; use sdf_core::dal_wrapper; use si_data_spicedb::SpiceDbClient; use si_db::HistoryActor; use si_events::{ ChangeSetApprovalStatus, workspace_snapshot::EntityKind, }; use si_frontend_types::RawGeometry; use si_id::EntityId; // FIXME(nick,jacob): this must happen in the "sdf_test"'s equivalent to global setup, but not in // dal tests. This also should _really_ reflect the "schema.zed" file that production uses. async fn write_schema(client: &mut SpiceDbClient) -> Result<()> { let schema = indoc! {" definition user {} definition workspace { relation approver: user relation owner: user permission approve = approver+owner permission manage = owner } "}; client.write_schema(schema).await?; Ok(()) } // NOTE(nick): this is an integration test and not a service test, but given that "sdf_test" is in // a weird, unused place at the time of writing, this test will live here. #[sdf_test] async fn single_user_relation_existence_and_checksum_validility_permutations( ctx: &mut DalContext, spicedb_client: SpiceDbClient, ) -> Result<()> { let mut spicedb_client = spicedb_client; // FIXME(nick,jacob): see the comment attached to this function. write_schema(&mut spicedb_client).await?; let workspace_id = ctx.workspace_pk()?; let user_id = match ctx.history_actor() { HistoryActor::SystemInit => return Err(eyre!("invalid user")), HistoryActor::User(user_id) => *user_id, }; // Scenario 1: create the variant without the relation. let (schema_variant_entity_id, schema_entity_id, first_approval_id) = { let schema = create_schema(ctx).await?; let (schema_variant, _) = SchemaVariant::new( ctx, schema.id(), "ringo starr", "ringo".to_string(), "beatles", "#FFFFFF", ComponentType::Component, None, None, None, false, ) .await?; let schema_variant_entity_id: EntityId = Ulid::from(schema_variant.id()).into(); let schema_entity_id: EntityId = Ulid::from(schema.id()).into(); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let approving_ids_with_hashes = dal_wrapper::change_set::new_approval_approving_ids_with_hashes( ctx, &mut spicedb_client, ) .await?; let first_approval = ChangeSetApproval::new( ctx, ChangeSetApprovalStatus::Approved, approving_ids_with_hashes, ) .await?; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements .iter_mut() .for_each(|r| r.applicable_approval_ids.sort()); frontend_requirements.sort_by_key(|req| req.entity_id); assert_eq!( vec![si_frontend_types::ChangeSetApproval { id: first_approval.id(), user_id, status: ChangeSetApprovalStatus::Approved, is_valid: true }], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_entity_id, entity_kind: EntityKind::Schema, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new(), )]), approver_individuals: Vec::new(), }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_variant_entity_id, entity_kind: EntityKind::SchemaVariant, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new(), )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); ( schema_variant_entity_id, schema_entity_id, first_approval.id(), ) }; // Scenario 2: create the relation and do not create another approval. The approval should be // invalid because its checksum was based on the root node. let relation = { let relation = RelationBuilder::new() .object(ObjectType::Workspace, workspace_id) .relation(Relation::Approver) .subject(ObjectType::User, user_id); relation.create(&mut spicedb_client).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements .iter_mut() .for_each(|r| r.applicable_approval_ids.sort()); frontend_requirements.sort_by_key(|req| req.entity_id); assert_eq!( vec![si_frontend_types::ChangeSetApproval { id: first_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: false }], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_entity_id, entity_kind: EntityKind::Schema, required_count: 1, is_satisfied: false, applicable_approval_ids: vec![first_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_variant_entity_id, entity_kind: EntityKind::SchemaVariant, required_count: 1, is_satisfied: false, applicable_approval_ids: vec![first_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); relation }; // Scenario 3: create an approval with our relation intact. Our new approval should satisfy the // requirement. let second_approval_id = { let approving_ids_with_hashes = dal_wrapper::change_set::new_approval_approving_ids_with_hashes( ctx, &mut spicedb_client, ) .await?; let second_approval = ChangeSetApproval::new( ctx, ChangeSetApprovalStatus::Approved, approving_ids_with_hashes, ) .await?; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (mut frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_latest_approvals.sort_by_key(|a| a.id); frontend_requirements .iter_mut() .for_each(|r| r.applicable_approval_ids.sort()); frontend_requirements.sort_by_key(|req| req.entity_id); assert_eq!( vec![ si_frontend_types::ChangeSetApproval { id: first_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: false }, si_frontend_types::ChangeSetApproval { id: second_approval.id(), user_id, status: ChangeSetApprovalStatus::Approved, is_valid: true } ], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_entity_id, entity_kind: EntityKind::Schema, required_count: 1, is_satisfied: true, applicable_approval_ids: vec![first_approval_id, second_approval.id()], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id] )]), approver_individuals: Vec::new(), }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_variant_entity_id, entity_kind: EntityKind::SchemaVariant, required_count: 1, is_satisfied: true, applicable_approval_ids: vec![first_approval_id, second_approval.id()], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); second_approval.id() }; // Scenario 4: delete the relation and do not create an approval. The newest approval for the // original permissions should still have a valid checksum, but it should not be applicable for // the requirement. { relation.delete(&mut spicedb_client).await?; let (mut frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_latest_approvals.sort_by_key(|a| a.id); frontend_requirements .iter_mut() .for_each(|r| r.applicable_approval_ids.sort()); frontend_requirements.sort_by_key(|req| req.entity_id); assert_eq!( vec![ si_frontend_types::ChangeSetApproval { id: first_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: true }, si_frontend_types::ChangeSetApproval { id: second_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: false } ], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_entity_id, entity_kind: EntityKind::Schema, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new() )]), approver_individuals: Vec::new(), }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_variant_entity_id, entity_kind: EntityKind::SchemaVariant, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new(), )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); } // Scenario 5: re-create the relation, but do not create an approval. The newest approval // should still have a valid checksum and it should satisfy the requirement, once again. { relation.create(&mut spicedb_client).await?; let (mut frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_latest_approvals.sort_by_key(|a| a.id); frontend_requirements .iter_mut() .for_each(|r| r.applicable_approval_ids.sort()); frontend_requirements.sort_by_key(|req| req.entity_id); assert_eq!( vec![ si_frontend_types::ChangeSetApproval { id: first_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: false }, si_frontend_types::ChangeSetApproval { id: second_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: true } ], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_entity_id, entity_kind: EntityKind::Schema, required_count: 1, is_satisfied: true, applicable_approval_ids: vec![first_approval_id, second_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id] )]), approver_individuals: Vec::new(), }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: schema_variant_entity_id, entity_kind: EntityKind::SchemaVariant, required_count: 1, is_satisfied: true, applicable_approval_ids: vec![first_approval_id, second_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); } Ok(()) } // NOTE(nick): this is an integration test and not a service test, but given that "sdf_test" is in // a weird, unused place at the time of writing, this test will live here. #[sdf_test] async fn individual_approver_for_view( ctx: &mut DalContext, spicedb_client: SpiceDbClient, ) -> Result<()> { let mut spicedb_client = spicedb_client; // FIXME(nick,jacob): see the comment attached to this function. write_schema(&mut spicedb_client).await?; // Cache the IDs we need. let workspace_id = ctx.workspace_pk()?; let user_id = match ctx.history_actor() { HistoryActor::SystemInit => return Err(eyre!("invalid user")), HistoryActor::User(user_id) => *user_id, }; let view_id = View::get_id_for_default(ctx).await?; // Scenario 1: see all approvals and requirements with an "empty" workspace. { let (frontend_latest_approvals, frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; assert!(frontend_latest_approvals.is_empty()); assert!(frontend_requirements.is_empty()); } // Scenario 2: add the approval requirement to the default view with ourself as the individual approver. let ( view_entity_id, approval_requirement_definition_entity_id, approval_requirement_definition_id, ) = { let approval_requirement_definition_id = ApprovalRequirement::new_definition( ctx, view_id, 1, HashSet::from([ApprovalRequirementApprover::User(user_id)]), ) .await?; let (view_entity_id, approval_requirement_definition_entity_id) = ( view_id.into_inner().into(), approval_requirement_definition_id.into_inner().into(), ); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements.sort_by_key(|r| r.entity_id); assert!(frontend_latest_approvals.is_empty()); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: view_entity_id, entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::new(), approver_individuals: vec![user_id] }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: approval_requirement_definition_entity_id, entity_kind: EntityKind::ApprovalRequirementDefinition, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new() )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); ( view_entity_id, approval_requirement_definition_entity_id, approval_requirement_definition_id, ) }; // Scenario 3: create an approval that will satisfy the view requirement, but not the // definition requirement. let first_approval_id = { let first_approval_id = { let approving_ids_with_hashes = dal_wrapper::change_set::new_approval_approving_ids_with_hashes( ctx, &mut spicedb_client, ) .await?; let first_approval = ChangeSetApproval::new( ctx, ChangeSetApprovalStatus::Approved, approving_ids_with_hashes, ) .await?; first_approval.id() }; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements.sort_by_key(|r| r.entity_id); assert_eq!( vec![si_frontend_types::ChangeSetApproval { id: first_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: true, }], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: view_entity_id, entity_kind: EntityKind::View, required_count: 1, is_satisfied: true, applicable_approval_ids: vec![first_approval_id], approver_groups: HashMap::new(), approver_individuals: vec![user_id] }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: approval_requirement_definition_entity_id, entity_kind: EntityKind::ApprovalRequirementDefinition, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new() )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); first_approval_id }; // Scenario 4: add ourself as a workspace approver. The original approval should no longer // satisfy the view requirement because the IDs we are approving has changed. It would also // not satisfy the definition requirement for the same reason. { let relation = RelationBuilder::new() .object(ObjectType::Workspace, workspace_id) .relation(Relation::Approver) .subject(ObjectType::User, user_id); relation.create(&mut spicedb_client).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements.sort_by_key(|r| r.entity_id); assert_eq!( vec![si_frontend_types::ChangeSetApproval { id: first_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: false, }], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: view_entity_id, entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: vec![first_approval_id], approver_groups: HashMap::new(), approver_individuals: vec![user_id] }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: approval_requirement_definition_entity_id, entity_kind: EntityKind::ApprovalRequirementDefinition, required_count: 1, is_satisfied: false, applicable_approval_ids: vec![first_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); }; // Scenario 5: create a rejection. { let second_approval_id = { let approving_ids_with_hashes = dal_wrapper::change_set::new_approval_approving_ids_with_hashes( ctx, &mut spicedb_client, ) .await?; let second_approval = ChangeSetApproval::new( ctx, ChangeSetApprovalStatus::Rejected, approving_ids_with_hashes, ) .await?; second_approval.id() }; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (mut frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_latest_approvals.sort_by_key(|a| a.id); frontend_requirements.sort_by_key(|r| r.entity_id); frontend_requirements .iter_mut() .for_each(|r| r.applicable_approval_ids.sort()); assert_eq!( vec![ si_frontend_types::ChangeSetApproval { id: first_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: false, }, si_frontend_types::ChangeSetApproval { id: second_approval_id, user_id, status: ChangeSetApprovalStatus::Rejected, is_valid: true, } ], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: view_entity_id, entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: vec![first_approval_id, second_approval_id], approver_groups: HashMap::new(), approver_individuals: vec![user_id] }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: approval_requirement_definition_entity_id, entity_kind: EntityKind::ApprovalRequirementDefinition, required_count: 1, is_satisfied: false, applicable_approval_ids: vec![first_approval_id, second_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); } // Scenario 6: create an approval that will satisfy both requirements. { let third_approval_id = { let approving_ids_with_hashes = dal_wrapper::change_set::new_approval_approving_ids_with_hashes( ctx, &mut spicedb_client, ) .await?; let third_approval = ChangeSetApproval::new( ctx, ChangeSetApprovalStatus::Approved, approving_ids_with_hashes, ) .await?; third_approval.id() }; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (mut frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_latest_approvals.sort_by_key(|a| a.id); frontend_requirements.sort_by_key(|r| r.entity_id); frontend_requirements .iter_mut() .for_each(|r| r.applicable_approval_ids.sort()); assert_eq!( vec![ si_frontend_types::ChangeSetApproval { id: first_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: false, }, si_frontend_types::ChangeSetApproval { id: third_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: true, } ], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: view_entity_id, entity_kind: EntityKind::View, required_count: 1, is_satisfied: true, applicable_approval_ids: vec![first_approval_id, third_approval_id], approver_groups: HashMap::new(), approver_individuals: vec![user_id] }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: approval_requirement_definition_entity_id, entity_kind: EntityKind::ApprovalRequirementDefinition, required_count: 1, is_satisfied: true, applicable_approval_ids: vec![first_approval_id, third_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); } // Scenario 7: apply the change set, create a new change set and observe that no approvals nor requirements exist. { ChangeSetTestHelpers::apply_change_set_to_base(ctx).await?; ChangeSetTestHelpers::fork_from_head_change_set(ctx).await?; let (frontend_latest_approvals, frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; assert!(frontend_latest_approvals.is_empty()); assert!(frontend_requirements.is_empty()); } // Scenario 8: remove the definiton from the view. { ApprovalRequirement::remove_definition(ctx, approval_requirement_definition_id).await?; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements.sort_by_key(|r| r.entity_id); assert!(frontend_latest_approvals.is_empty()); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: view_entity_id, entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: approval_requirement_definition_entity_id, entity_kind: EntityKind::ApprovalRequirementDefinition, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), }, ], // expected frontend_requirements // actual ); } // Scenario 9: reject the removal. { let fourth_approval_id = { let approving_ids_with_hashes = dal_wrapper::change_set::new_approval_approving_ids_with_hashes( ctx, &mut spicedb_client, ) .await?; let fourth_approval = ChangeSetApproval::new( ctx, ChangeSetApprovalStatus::Rejected, approving_ids_with_hashes, ) .await?; fourth_approval.id() }; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements.sort_by_key(|r| r.entity_id); assert_eq!( vec![si_frontend_types::ChangeSetApproval { id: fourth_approval_id, user_id, status: ChangeSetApprovalStatus::Rejected, is_valid: true, },], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: view_entity_id, entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: vec![fourth_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: approval_requirement_definition_entity_id, entity_kind: EntityKind::ApprovalRequirementDefinition, required_count: 1, is_satisfied: false, applicable_approval_ids: vec![fourth_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); } // Scenario 10: approve the removal. { let fifth_approval_id = { let approving_ids_with_hashes = dal_wrapper::change_set::new_approval_approving_ids_with_hashes( ctx, &mut spicedb_client, ) .await?; let fifth_approval = ChangeSetApproval::new( ctx, ChangeSetApprovalStatus::Approved, approving_ids_with_hashes, ) .await?; fifth_approval.id() }; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements.sort_by_key(|r| r.entity_id); assert_eq!( vec![si_frontend_types::ChangeSetApproval { id: fifth_approval_id, user_id, status: ChangeSetApprovalStatus::Approved, is_valid: true, },], // expected frontend_latest_approvals // actual ); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: view_entity_id, entity_kind: EntityKind::View, required_count: 1, is_satisfied: true, applicable_approval_ids: vec![fifth_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: approval_requirement_definition_entity_id, entity_kind: EntityKind::ApprovalRequirementDefinition, required_count: 1, is_satisfied: true, applicable_approval_ids: vec![fifth_approval_id], approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), vec![user_id], )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); } Ok(()) } // NOTE(nick): this is an integration test and not a service test, but given that "sdf_test" is in // a weird, unused place at the time of writing, this test will live here. #[sdf_test] async fn one_component_in_two_views( ctx: &mut DalContext, spicedb_client: SpiceDbClient, ) -> Result<()> { let mut spicedb_client = spicedb_client; // FIXME(nick,jacob): see the comment attached to this function. write_schema(&mut spicedb_client).await?; // Cache the IDs we need. let workspace_id = ctx.workspace_pk()?; let user_id = match ctx.history_actor() { HistoryActor::SystemInit => return Err(eyre!("invalid user")), HistoryActor::User(user_id) => *user_id, }; // Create a view with a requirement and then commit. let todd_view = View::new(ctx, "toddhoward").await?; let todd_view_id = todd_view.id(); ApprovalRequirement::new_definition( ctx, todd_view_id, 1, HashSet::from([ApprovalRequirementApprover::User(user_id)]), ) .await?; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; // Create a second view with another requirement and then commit. let sven_view = View::new(ctx, "svenvincke").await?; let sven_view_id = sven_view.id(); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; // Scenario 1: apply to HEAD and create a new change set. { ChangeSetTestHelpers::apply_change_set_to_base(ctx).await?; ChangeSetTestHelpers::fork_from_head_change_set(ctx).await?; let (frontend_latest_approvals, frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; assert!(frontend_latest_approvals.is_empty()); assert!(frontend_requirements.is_empty()); } // Scenario 2: create a component in our new views. let component = { let component = create_component_for_default_schema_name( ctx, "starfield", "shattered space", todd_view_id, ) .await?; Component::add_to_view(ctx, component.id(), sven_view_id, RawGeometry::default()).await?; let mut queued_actions = Action::find_for_component_id(ctx, component.id()).await?; assert_eq!(1, queued_actions.len()); let _action_id = queued_actions .pop() .expect("Unable to get first element of a single element Vec"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements.sort_by_key(|r| r.entity_id); assert!(frontend_latest_approvals.is_empty()); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: todd_view_id.into_inner().into(), entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::new(), approver_individuals: vec![user_id], }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: sven_view_id.into_inner().into(), entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new() )]), approver_individuals: Vec::new(), }, ], // expected frontend_requirements // actual ); component }; // Scenario 3: apply to HEAD and create a new change set (skip approvals). { ChangeSetTestHelpers::apply_change_set_to_base(ctx).await?; ChangeSetTestHelpers::wait_for_actions_to_run(ctx).await?; ChangeSetTestHelpers::fork_from_head_change_set(ctx).await?; let (frontend_latest_approvals, frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; assert!(frontend_latest_approvals.is_empty()); assert!(frontend_requirements.is_empty()); } // Scenario 4: modify the component in our new view. { component.set_name(ctx, "bg3 patch 8 plz").await?; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements.sort_by_key(|r| r.entity_id); assert!(frontend_latest_approvals.is_empty()); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: todd_view_id.into_inner().into(), entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::new(), approver_individuals: vec![user_id], }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: sven_view_id.into_inner().into(), entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new() )]), approver_individuals: Vec::new(), }, ], // expected frontend_requirements // actual ); } // Scenario 5: apply to HEAD and create a new change set (skip approvals). { ChangeSetTestHelpers::apply_change_set_to_base(ctx).await?; ChangeSetTestHelpers::wait_for_actions_to_run(ctx).await?; ChangeSetTestHelpers::fork_from_head_change_set(ctx).await?; let (frontend_latest_approvals, frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; assert!(frontend_latest_approvals.is_empty()); assert!(frontend_requirements.is_empty()); } // Scenario 6: delete the component in our new view. { component.delete(ctx).await?; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let (frontend_latest_approvals, mut frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; frontend_requirements.sort_by_key(|r| r.entity_id); assert!(frontend_latest_approvals.is_empty()); assert_eq!( vec![ si_frontend_types::ChangeSetApprovalRequirement { entity_id: todd_view_id.into_inner().into(), entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::new(), approver_individuals: vec![user_id], }, si_frontend_types::ChangeSetApprovalRequirement { entity_id: sven_view_id.into_inner().into(), entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new() )]), approver_individuals: Vec::new(), } ], // expected frontend_requirements // actual ); } // Scenario 7: apply to HEAD and create a new change set (skip approvals). { ChangeSetTestHelpers::apply_change_set_to_base(ctx).await?; ChangeSetTestHelpers::wait_for_actions_to_run(ctx).await?; ChangeSetTestHelpers::fork_from_head_change_set(ctx).await?; let (frontend_latest_approvals, frontend_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; assert!(frontend_latest_approvals.is_empty()); assert!(frontend_requirements.is_empty()); } Ok(()) } #[sdf_test] async fn list_approval_requirement_definitions_for_entity( ctx: &mut DalContext, spicedb_client: SpiceDbClient, ) -> Result<()> { let mut spicedb_client = spicedb_client; // FIXME(nick,jacob): see the comment attached to this function. write_schema(&mut spicedb_client).await?; let user_id = match ctx.history_actor() { HistoryActor::SystemInit => return Err(eyre!("invalid user")), HistoryActor::User(user_id) => *user_id, }; let second_view = View::new(ctx, "Second view").await?; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let initial_definitions = ApprovalRequirementDefinition::list_for_entity_id(ctx, second_view.id()).await?; assert!(initial_definitions.is_empty()); let requirement_definition_id = ApprovalRequirement::new_definition( ctx, second_view.id(), 1, [ApprovalRequirementApprover::User(user_id)] .iter() .cloned() .collect(), ) .await?; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; let explicit_definitions = ApprovalRequirementDefinition::list_for_entity_id(ctx, second_view.id()).await?; assert_eq!( vec![ApprovalRequirementDefinition { id: requirement_definition_id, required_count: 1, approvers: [ApprovalRequirementApprover::User(user_id)] .iter() .cloned() .collect() }], explicit_definitions, ); Ok(()) } #[sdf_test] async fn approval_requirement_created_on_action_enqueue( ctx: &mut DalContext, spicedb_client: SpiceDbClient, ) -> Result<()> { let mut spicedb_client = spicedb_client; write_schema(&mut spicedb_client).await?; // Cache the IDs we need. let workspace_id = ctx.workspace_pk()?; let view_id = View::get_id_for_default(ctx).await?; ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; // Check that there are no approval requirements initially let (initial_approvals, initial_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; assert!(initial_approvals.is_empty()); assert!(initial_requirements.is_empty()); // Create a component and add it to the view (this should enqueue an action) let component = create_component_for_default_schema_name_in_default_view( ctx, "starfield", "shattered space", ) .await?; let queued_actions = Action::find_for_component_id(ctx, component.id()).await?; assert_eq!(1, queued_actions.len()); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx).await?; // Check that an approval requirement has been created let (_, mut updated_requirements) = dal_wrapper::change_set::status(ctx, &mut spicedb_client).await?; updated_requirements.sort_by_key(|r| r.entity_id); assert_eq!( vec![si_frontend_types::ChangeSetApprovalRequirement { entity_id: view_id.into_inner().into(), entity_kind: EntityKind::View, required_count: 1, is_satisfied: false, applicable_approval_ids: Vec::new(), approver_groups: HashMap::from_iter(vec![( format!("workspace#{workspace_id}#approve"), Vec::new() )]), approver_individuals: Vec::new(), },], updated_requirements ); Ok(()) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server