Skip to main content
Glama
change_set.rs22.8 kB
use std::collections::HashSet; use dal::{ AccessBuilder, ChangeSet, ChangeSetStatus, Component, DalContext, DalContextBuilder, RequestContext, Workspace, WorkspacePk, change_set::view::OpenChangeSetsView, context::TransactionsErrorDiscriminants, }; use dal_test::{ helpers::{ ChangeSetTestHelpers, create_component_for_default_schema_name_in_default_view, create_user, }, test, }; use itertools::Itertools; use pretty_assertions_sorted::assert_eq; use si_db::HistoryActor; use si_events::{ AuthenticationMethod, AuthenticationMethodRole, authentication_method::AuthenticationMethodV1, }; mod approval; #[test] async fn open_change_sets(ctx: &mut DalContext) { let view = OpenChangeSetsView::assemble(ctx) .await .expect("could not assemble view"); // Check that the expected number of open change sets exist. assert_eq!( 2, // expected view.change_sets.len() // actual ); // Check that we collected "head" properly. let head_change_set_id = ctx .get_workspace_default_change_set_id() .await .expect("could not get the default change set id for the workspace"); assert_eq!( head_change_set_id, // expected view.head_change_set_id // actual ); // Ensure that the current change set is not "head". let current_change_set_id = ctx.change_set_id(); assert_ne!(current_change_set_id, head_change_set_id); // Ensure that the views contain the change sets that we expect. let change_set_ids = HashSet::from_iter(view.change_sets.iter().map(|c| c.id)); assert_eq!( HashSet::from([current_change_set_id, head_change_set_id]), // expected change_set_ids, // actual ); // Apply the change set and perform a blocking commit. ChangeSetTestHelpers::apply_change_set_to_base(ctx) .await .expect("could not apply change set"); // Assemble the view again and ensure only "head" exists. let mut view = OpenChangeSetsView::assemble(ctx) .await .expect("could not assemble view"); // Check that the expected number of open change sets exist. There should only be one. let head_change_set_view = view.change_sets.pop().expect("change sets are empty"); assert!(view.change_sets.is_empty()); assert_eq!( view.head_change_set_id, // expected head_change_set_view.id // actual ); assert_eq!( head_change_set_id, // expected head_change_set_view.id // actual ); // Create a new change set off HEAD. ChangeSetTestHelpers::fork_from_head_change_set(ctx) .await .expect("could not fork change set"); // List views again. let view = OpenChangeSetsView::assemble(ctx) .await .expect("could not assemble view"); // Check that the expected number of open change sets exist... again. assert_eq!( 2, // expected view.change_sets.len() // actual ); // Check that we collected "head" properly... again. let head_change_set_id_again = ctx .get_workspace_default_change_set_id() .await .expect("could not get the default change set id for the workspace"); assert_eq!( head_change_set_id, // expected head_change_set_id_again // actual ); assert_eq!( head_change_set_id, // expected view.head_change_set_id // actual ); // Ensure that the current change set is not "head"... again. let current_change_set_id_again = ctx.change_set_id(); assert_ne!(head_change_set_id, current_change_set_id_again); assert_ne!(current_change_set_id, current_change_set_id_again); // Ensure that the views contain the change sets that we expect... again. let change_set_ids_again = HashSet::from_iter(view.change_sets.iter().map(|c| c.id)); assert_eq!( HashSet::from([current_change_set_id_again, head_change_set_id_again]), // expected change_set_ids_again, // actual ); } pub const TEST_JWT_AUTHENTICATION_METHOD: AuthenticationMethod = AuthenticationMethodV1::Jwt { role: AuthenticationMethodRole::Web, token_id: None, }; #[test] async fn abandon_change_set_and_check_open_change_sets(ctx: &mut DalContext) { let change_set_name = "for abandonment".to_string(); ChangeSetTestHelpers::fork_from_head_change_set_with_name(ctx, &change_set_name) .await .expect("could not fork change set"); // List open changesets. let view = OpenChangeSetsView::assemble(ctx) .await .expect("could not assemble view"); // Check that the expected number of change sets exist.... assert_eq!( 3, // expected view.change_sets.len() // actual ); ChangeSetTestHelpers::abandon_change_set(ctx) .await .expect("could not abandon change set"); // relist the open changesets. let view = OpenChangeSetsView::assemble(ctx) .await .expect("could not assemble view"); // Check that we no longer have the abandoned changeset assert_eq!( 2, // expected view.change_sets.len() // actual ); let change_set_names = Vec::from_iter(view.change_sets.iter().map(|c| c.name.clone())); assert!(!change_set_names.contains(&change_set_name)) } #[test] async fn build_from_request_context_limits_to_workspaces_user_has_access_to( ctx: &mut DalContext, ctx_builder: DalContextBuilder, ) { let user_1 = create_user(ctx).await.expect("Unable to create user"); let user_2 = create_user(ctx).await.expect("Unable to create user"); let user_1_workspace = Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_1 workspace", "token") .await .expect("Unable to create workspace"); let user_2_workspace = Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_2 workspace", "token") .await .expect("Unable to create workspace"); user_1 .associate_workspace(ctx, *user_1_workspace.pk()) .await .expect("Unable to associate user with workspace"); user_2 .associate_workspace(ctx, *user_2_workspace.pk()) .await .expect("Unable to associate user with workspace"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("Unable to set up test data"); let request_context = RequestContext { tenancy: si_db::Tenancy::new(*user_2_workspace.pk()), visibility: si_db::Visibility { change_set_id: user_2_workspace.default_change_set_id(), }, history_actor: HistoryActor::User(user_1.pk()), request_ulid: None, authentication_method: TEST_JWT_AUTHENTICATION_METHOD, }; let builder_result = ctx_builder.build(request_context).await; assert!( builder_result .is_err_and(|e| TransactionsErrorDiscriminants::BadWorkspaceAndChangeSet == e.into()) ); let request_context = RequestContext { tenancy: si_db::Tenancy::new(*user_1_workspace.pk()), visibility: si_db::Visibility { change_set_id: user_1_workspace.default_change_set_id(), }, history_actor: HistoryActor::User(user_2.pk()), request_ulid: None, authentication_method: TEST_JWT_AUTHENTICATION_METHOD, }; let builder_result = ctx_builder.build(request_context).await; assert!( builder_result .is_err_and(|e| TransactionsErrorDiscriminants::BadWorkspaceAndChangeSet == e.into()) ); } #[test] async fn build_from_request_context_limits_to_change_sets_of_current_workspace( ctx: &mut DalContext, ctx_builder: DalContextBuilder, ) { let user_1 = create_user(ctx).await.expect("Unable to create user"); let user_2 = create_user(ctx).await.expect("Unable to create user"); let user_1_workspace = Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_1 workspace", "token") .await .expect("Unable to create workspace"); let user_2_workspace = Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_2 workspace", "token") .await .expect("Unable to create workspace"); user_1 .associate_workspace(ctx, *user_1_workspace.pk()) .await .expect("Unable to associate user with workspace"); user_2 .associate_workspace(ctx, *user_2_workspace.pk()) .await .expect("Unable to associate user with workspace"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("Unable to set up test data"); let request_context = RequestContext { tenancy: si_db::Tenancy::new(*user_1_workspace.pk()), visibility: si_db::Visibility { change_set_id: user_2_workspace.default_change_set_id(), }, history_actor: HistoryActor::User(user_1.pk()), request_ulid: None, authentication_method: TEST_JWT_AUTHENTICATION_METHOD, }; let builder_result = ctx_builder.build(request_context).await; assert!( builder_result .is_err_and(|e| TransactionsErrorDiscriminants::BadWorkspaceAndChangeSet == e.into()) ); let request_context = RequestContext { tenancy: si_db::Tenancy::new(*user_2_workspace.pk()), visibility: si_db::Visibility { change_set_id: user_1_workspace.default_change_set_id(), }, history_actor: HistoryActor::User(user_2.pk()), request_ulid: None, authentication_method: TEST_JWT_AUTHENTICATION_METHOD, }; let builder_result = ctx_builder.build(request_context).await; assert!( builder_result .is_err_and(|e| TransactionsErrorDiscriminants::BadWorkspaceAndChangeSet == e.into()) ); } #[test] async fn cannot_find_change_set_across_workspaces( ctx: &mut DalContext, ctx_builder: DalContextBuilder, ) { let user_1 = create_user(ctx).await.expect("Unable to create user"); let user_2 = create_user(ctx).await.expect("Unable to create user"); let user_1_workspace = Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_1 workspace", "token") .await .expect("Unable to create workspace"); let user_2_workspace = Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_2 workspace", "token") .await .expect("Unable to create workspace"); user_1 .associate_workspace(ctx, *user_1_workspace.pk()) .await .expect("Unable to associate user with workspace"); user_2 .associate_workspace(ctx, *user_2_workspace.pk()) .await .expect("Unable to associate user with workspace"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("Unable to set up test data"); let request_context = RequestContext { tenancy: si_db::Tenancy::new(*user_2_workspace.pk()), visibility: si_db::Visibility { change_set_id: user_2_workspace.default_change_set_id(), }, history_actor: HistoryActor::User(user_2.pk()), request_ulid: None, authentication_method: TEST_JWT_AUTHENTICATION_METHOD, }; let mut user_2_dal_ctx = ctx_builder .build(request_context) .await .expect("built dal ctx for user 2"); //create a new change set for user 2 let user_2_change_set = ChangeSet::fork_head(&user_2_dal_ctx, "user 2") .await .expect("could not create change set"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(&mut user_2_dal_ctx) .await .expect("Unable to set up test data"); let user_1_tenancy = si_db::Tenancy::new(*user_1_workspace.pk()); let access_builder = AccessBuilder::new( user_1_tenancy, HistoryActor::User(user_1.pk()), None, TEST_JWT_AUTHENTICATION_METHOD, ); let user_1_dal_context = ctx_builder .build_head(access_builder) .await .expect("could not build dal context"); //first, let's ensure we can't find it when using the user 1 dal ctx let user_2_change_set_unfound = ChangeSet::find(&user_1_dal_context, user_2_change_set.id) .await .expect("could not find change set"); assert!(user_2_change_set_unfound.is_none()); // But if we search for the change set across all workspaces, we find it ChangeSet::get_by_id_across_workspaces(&user_1_dal_context, user_2_change_set.id) .await .expect("could not find change set"); } #[test] async fn build_from_request_context_allows_change_set_from_workspace_with_access( ctx: &mut DalContext, ctx_builder: DalContextBuilder, ) { let user_1 = create_user(ctx).await.expect("Unable to create user"); let user_2 = create_user(ctx).await.expect("Unable to create user"); let user_1_workspace = Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_1 workspace", "token") .await .expect("Unable to create workspace"); let user_2_workspace = Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_2 workspace", "token") .await .expect("Unable to create workspace"); user_1 .associate_workspace(ctx, *user_1_workspace.pk()) .await .expect("Unable to associate user with workspace"); user_2 .associate_workspace(ctx, *user_2_workspace.pk()) .await .expect("Unable to associate user with workspace"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("Unable to set up test data"); let request_context = RequestContext { tenancy: si_db::Tenancy::new(*user_1_workspace.pk()), visibility: si_db::Visibility { change_set_id: user_1_workspace.default_change_set_id(), }, history_actor: HistoryActor::User(user_1.pk()), request_ulid: None, authentication_method: TEST_JWT_AUTHENTICATION_METHOD, }; let builder_result = ctx_builder.build(request_context).await; if let Err(e) = &builder_result { dbg!(e); } assert!(builder_result.is_ok()); let request_context = RequestContext { tenancy: si_db::Tenancy::new(*user_2_workspace.pk()), visibility: si_db::Visibility { change_set_id: user_2_workspace.default_change_set_id(), }, history_actor: HistoryActor::User(user_2.pk()), request_ulid: None, authentication_method: TEST_JWT_AUTHENTICATION_METHOD, }; let builder_result = ctx_builder.build(request_context).await; if let Err(e) = &builder_result { dbg!(e); } assert!(builder_result.is_ok()); } #[test] async fn change_set_approval_flow(ctx: &mut DalContext) { // create a new change set let new_change_set = ChangeSetTestHelpers::fork_from_head_change_set(ctx) .await .expect("could not fork head"); let current_user = ChangeSet::extract_userid_from_context(ctx).await; // do something in it let component = create_component_for_default_schema_name_in_default_view(ctx, "small odd lego", "small") .await .expect("could not create component"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("could not commit and update"); // request approval let mut change_set = ChangeSet::get_by_id(ctx, new_change_set.id) .await .expect("could not find change set"); change_set .request_change_set_approval(ctx) .await .expect("could not request approval"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("could not commit and update"); let mut change_set = ChangeSet::get_by_id(ctx, new_change_set.id) .await .expect("could not find change set"); // make sure everything looks right assert_eq!(change_set.status, ChangeSetStatus::NeedsApproval); assert!(change_set.merge_requested_at.is_some()); assert_eq!(change_set.merge_requested_by_user_id, current_user); assert_eq!(change_set.reviewed_at, None); assert_eq!(change_set.reviewed_by_user_id, None); // let's reject it change_set .reject_change_set_for_apply(ctx) .await .expect("could not reject change set"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("could not commit and update"); let mut change_set = ChangeSet::get_by_id(ctx, new_change_set.id) .await .expect("could not find change set"); assert_eq!(change_set.status, ChangeSetStatus::Rejected); assert!(change_set.merge_requested_at.is_some()); assert_eq!(change_set.merge_requested_by_user_id, current_user); assert!(change_set.reviewed_at.is_some()); assert_eq!(change_set.reviewed_by_user_id, current_user); // let's see if we can apply now, it should fail because the change set has not been approved let apply_result = ChangeSetTestHelpers::apply_change_set_to_base_approvals(ctx).await; assert!(apply_result.is_err()); // now let's re-open it change_set .reopen_change_set(ctx) .await .expect("could not update status"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("could not commit and update"); let mut change_set = ChangeSet::get_by_id(ctx, new_change_set.id) .await .expect("could not find change set"); assert_eq!(change_set.status, ChangeSetStatus::Open); assert_eq!(change_set.merge_requested_at, None); assert_eq!(change_set.merge_requested_by_user_id, None); assert_eq!(change_set.reviewed_at, None); assert_eq!(change_set.reviewed_by_user_id, None); // now let's request approval again change_set .request_change_set_approval(ctx) .await .expect("could not request approval"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("could not commit and update"); let mut change_set = ChangeSet::get_by_id(ctx, new_change_set.id) .await .expect("could not find change set"); // make sure everything looks right assert_eq!(change_set.status, ChangeSetStatus::NeedsApproval); assert!(change_set.merge_requested_at.is_some()); assert_eq!(change_set.merge_requested_by_user_id, current_user); assert_eq!(change_set.reviewed_at, None); assert_eq!(change_set.reviewed_by_user_id, None); // this time we will approve change_set .approve_change_set_for_apply(ctx) .await .expect("could not approve"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("could not commit and update"); let change_set = ChangeSet::get_by_id(ctx, new_change_set.id) .await .expect("could not find change set"); // make sure everything looks right assert_eq!(change_set.status, ChangeSetStatus::Approved); assert!(change_set.merge_requested_at.is_some()); assert_eq!(change_set.merge_requested_by_user_id, current_user); assert!(change_set.reviewed_at.is_some()); assert_eq!(change_set.reviewed_by_user_id, current_user); // now let's apply it! ChangeSetTestHelpers::apply_change_set_to_base_approvals(ctx) .await .expect("could not apply to head"); // should have one component let mut components = Component::list(ctx) .await .expect("could not list components"); assert_eq!(components.len(), 1); let only_component = components.pop().expect("has one in there"); assert_eq!(only_component.id(), component.id()); // now let's create another change set and ensure force_apply works as expected let _new_change_set = ChangeSetTestHelpers::fork_from_head_change_set(ctx) .await .expect("could not fork head"); // do something in it let second_component = create_component_for_default_schema_name_in_default_view(ctx, "small odd lego", "small") .await .expect("could not create component"); ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx) .await .expect("could not commit and update"); // force apply ChangeSetTestHelpers::force_apply_change_set_to_base_approvals(ctx) .await .expect("could not force apply change set"); // should have two components now let components = Component::list(ctx) .await .expect("could not list components"); assert_eq!(components.len(), 2); let component_ids = [component.id(), second_component.id()]; let components = components .into_iter() .filter(|comp| component_ids.contains(&comp.id())) .collect_vec(); assert_eq!(components.len(), 2); } #[test] async fn update_pointer_tracks_last_used(ctx: &mut DalContext) { // Create change set with initial snapshot let mut change_set = ChangeSet::fork_head(ctx, "test-tracking") .await .expect("could not fork head"); let old_snapshot = change_set.workspace_snapshot_address; // Create a new snapshot to update to let new_snapshot_address = ctx .workspace_snapshot() .expect("could not get workspace snapshot") .write(ctx) .await .expect("could not write snapshot"); // Update pointer change_set .update_pointer(ctx, new_snapshot_address) .await .expect("could not update pointer"); // Verify old snapshot tracked in snapshot_last_used let row = ctx .txns() .await .expect("could not get txns") .pg() .query_opt( "SELECT snapshot_id, last_used_at FROM snapshot_last_used WHERE snapshot_id = $1", &[&old_snapshot.to_string()], ) .await .expect("could not query snapshot_last_used"); assert!(row.is_some(), "Old snapshot should be tracked"); let row = row.unwrap(); let snapshot_id: String = row .try_get("snapshot_id") .expect("could not get snapshot_id"); assert_eq!(snapshot_id, old_snapshot.to_string()); }

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