Skip to content

Commit

Permalink
Do not delete frames if dependent components have resources
Browse files Browse the repository at this point in the history
This commit ensures that we do not delete frames if dependent components
have resources.

The original fix is from @britmyerss and @nickgerace mainly rebased the
fix, added tests, and provided miscellaneous changes.

Secondary changes:
- Block the commit when waiting for actions in tests
- Add delete action to fallout test exclusive schema
- Add test helper for fetching resource last synced value

Misc changes:
- Remove async requirement from "build_action_func" helper
- Add "Component::view_by_id" and have "Component::view" call it

Signed-off-by: Nick Gerace <[email protected]>
Co-authored-by: Brit Myers <[email protected]>
  • Loading branch information
britmyerss authored and nickgerace committed Jun 6, 2024
1 parent ad934d4 commit 1609391
Show file tree
Hide file tree
Showing 16 changed files with 590 additions and 118 deletions.
46 changes: 44 additions & 2 deletions lib/dal-test/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
//! This module contains helpers for use when authoring dal integration tests.
use async_recursion::async_recursion;
use dal::attribute::value::AttributeValueError;
use dal::key_pair::KeyPairPk;
use dal::property_editor::schema::{
PropertyEditorProp, PropertyEditorPropKind, PropertyEditorSchema,
};
use dal::property_editor::values::{PropertyEditorValue, PropertyEditorValues};
use dal::property_editor::{PropertyEditorPropId, PropertyEditorValueId};
use dal::{
AttributeValue, Component, ComponentId, DalContext, InputSocket, KeyPair, OutputSocket, Prop,
Schema, SchemaVariant, SchemaVariantId, User, UserClaim, UserPk,
AttributeValue, Component, ComponentError, ComponentId, DalContext, InputSocket, KeyPair,
OutputSocket, Prop, Schema, SchemaVariant, SchemaVariantId, User, UserClaim, UserPk,
};
use itertools::enumerate;
use jwt_simple::algorithms::RSAKeyPairLike;
Expand All @@ -18,6 +19,7 @@ use names::{Generator, Name};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use thiserror::Error;

use crate::jwt_private_signing_key;

Expand All @@ -26,6 +28,21 @@ mod change_set;
pub use change_set::ChangeSetTestHelpers;
pub use change_set::ChangeSetTestHelpersError;

#[allow(missing_docs)]
#[derive(Debug, Error)]
pub enum DalTestHelpersError {
#[error("attribute value error: {0}")]
AttributeValue(#[from] AttributeValueError),
#[error("component error: {0}")]
Component(#[from] ComponentError),
#[error("too many attribute values found")]
TooManyAttributeValues,
#[error("unexpected empty attribute values found")]
UnexpectedEmptyAttributeValues,
}

type DalTestHelpersResult<T> = Result<T, DalTestHelpersError>;

/// Generates a fake name.
pub fn generate_fake_name() -> String {
Generator::with_naming(Name::Numbered)
Expand Down Expand Up @@ -254,6 +271,31 @@ pub async fn encrypt_message(
crypted
}

/// Fetches the value stored at "/root/resource/last_synced" for the provided [`Component`].
pub async fn fetch_resource_last_synced_value(
ctx: &DalContext,
component_id: ComponentId,
) -> DalTestHelpersResult<Option<serde_json::Value>> {
let mut attribute_value_ids = Component::attribute_values_for_prop_by_id(
ctx,
component_id,
&["root", "resource", "last_synced"],
)
.await?;
let attribute_value_id = attribute_value_ids
.pop()
.ok_or(DalTestHelpersError::UnexpectedEmptyAttributeValues)?;
if !attribute_value_ids.is_empty() {
return Err(DalTestHelpersError::TooManyAttributeValues);
}

let last_synced_value = AttributeValue::get_by_id(ctx, attribute_value_id)
.await?
.view(ctx)
.await?;
Ok(last_synced_value)
}

#[allow(missing_docs)]
#[derive(Serialize, Deserialize, Debug)]
pub struct PropEditorTestView {
Expand Down
21 changes: 13 additions & 8 deletions lib/dal-test/src/helpers/change_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,7 @@ impl ChangeSetTestHelpers {
pub async fn commit_and_update_snapshot_to_visibility(
ctx: &mut DalContext,
) -> ChangeSetTestHelpersResult<()> {
// TODO(nick,brit): we need to expand Brit's 409 conflict work to work with blocking commits
// too rather than evaluating an optional set of conflicts.
if let Some(conflicts) = ctx.blocking_commit().await? {
return Err(ChangeSetTestHelpersError::ConflictsFoundAfterCommit(
conflicts,
));
}
Self::blocking_commit(ctx).await?;
ctx.update_snapshot_to_visibility().await?;
Ok(())
}
Expand Down Expand Up @@ -111,7 +105,7 @@ impl ChangeSetTestHelpers {
Ok(change_set) => change_set,
};

ctx.commit().await?;
Self::blocking_commit(ctx).await?;

ctx.update_visibility_and_snapshot_to_visibility_no_editing_change_set(
applied_change_set.base_change_set_id.ok_or(
Expand Down Expand Up @@ -163,4 +157,15 @@ impl ChangeSetTestHelpers {

Ok(new_change_set)
}

async fn blocking_commit(ctx: &DalContext) -> ChangeSetTestHelpersResult<()> {
// TODO(nick,brit): we need to expand Brit's 409 conflict work to work with blocking commits
// too rather than evaluating an optional set of conflicts.
match ctx.blocking_commit().await? {
Some(conflicts) => Err(ChangeSetTestHelpersError::ConflictsFoundAfterCommit(
conflicts,
)),
None => Ok(()),
}
}
}
5 changes: 1 addition & 4 deletions lib/dal-test/src/schemas/schema_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ pub(crate) async fn build_resource_payload_to_value_func() -> BuiltinsResult<Fun
Ok(resource_payload_to_value_func)
}

pub(crate) async fn build_action_func(
code: &str,
fn_name: &str,
) -> Result<FuncSpec, BuiltinsError> {
pub(crate) fn build_action_func(code: &str, fn_name: &str) -> Result<FuncSpec, BuiltinsError> {
let func = FuncSpec::builder()
.name(fn_name)
.unique_id(fn_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ pub(crate) async fn migrate_test_exclusive_schema_large_odd_lego(
}";

let fn_name = "test:createActionLargeLego";
let create_action_func = build_action_func(create_action_code, fn_name).await?;
let create_action_func = build_action_func(create_action_code, fn_name)?;

// Build Refresh Action Func
let refresh_action_code = "async function main(component: Input): Promise<Output> {
return { payload: JSON.parse(component.properties.resource?.payload) || { \"poop\": true } , status: \"ok\" };
}";

let fn_name = "test:refreshActionLargeLego";
let refresh_action_func = build_action_func(refresh_action_code, fn_name).await?;
let refresh_action_func = build_action_func(refresh_action_code, fn_name)?;

let update_action_code = "async function main(component: Input): Promise<Output> {
return { payload: { \"poonami\": true }, status: \"ok\" };
}";
let fn_name = "test:updateActionLargeLego";
let update_action_func = build_action_func(update_action_code, fn_name).await?;
let update_action_func = build_action_func(update_action_code, fn_name)?;

// Create Scaffold Func
let fn_name = "test:scaffoldLargeLegoAsset";
Expand Down Expand Up @@ -151,21 +151,21 @@ pub(crate) async fn migrate_test_exclusive_schema_large_even_lego(
}";

let fn_name = "test:createActionLargeLego";
let create_action_func = build_action_func(create_action_code, fn_name).await?;
let create_action_func = build_action_func(create_action_code, fn_name)?;

// Build Refresh Action Func
let refresh_action_code = "async function main(component: Input): Promise<Output> {
return { payload: JSON.parse(component.properties.resource?.payload) || { \"poop\": true } , status: \"ok\" };
}";

let fn_name = "test:refreshActionLargeLego";
let refresh_action_func = build_action_func(refresh_action_code, fn_name).await?;
let refresh_action_func = build_action_func(refresh_action_code, fn_name)?;

let update_action_code = "async function main(component: Input): Promise<Output> {
return { payload: { \"poonami\": true }, status: \"ok\" };
}";
let fn_name = "test:updateActionLargeLego";
let update_action_func = build_action_func(update_action_code, fn_name).await?;
let update_action_func = build_action_func(update_action_code, fn_name)?;

// Create Scaffold Func
let fn_name = "test:scaffoldLargeLegoAsset";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ pub(crate) async fn migrate_test_exclusive_schema_medium_odd_lego(
}";

let fn_name = "test:createActionMediumLego";
let create_action_func = build_action_func(create_action_code, fn_name).await?;
let create_action_func = build_action_func(create_action_code, fn_name)?;

// Build Refresh Action Func
let refresh_action_code = "async function main(component: Input): Promise<Output> {
return { payload: JSON.parse(component.properties.resource?.payload) || { \"poop\": true } , status: \"ok\" };
}";

let fn_name = "test:refreshActionMediumLego";
let refresh_action_func = build_action_func(refresh_action_code, fn_name).await?;
let refresh_action_func = build_action_func(refresh_action_code, fn_name)?;

let update_action_code = "async function main(component: Input): Promise<Output> {
return { payload: { \"poonami\": true }, status: \"ok\" };
}";
let fn_name = "test:updateActionMediumLego";
let update_action_func = build_action_func(update_action_code, fn_name).await?;
let update_action_func = build_action_func(update_action_code, fn_name)?;

// Create Scaffold Func
let fn_name = "test:scaffoldMediumLegoAsset";
Expand Down Expand Up @@ -146,21 +146,21 @@ pub(crate) async fn migrate_test_exclusive_schema_medium_even_lego(
}";

let fn_name = "test:createActionMediumLego";
let create_action_func = build_action_func(create_action_code, fn_name).await?;
let create_action_func = build_action_func(create_action_code, fn_name)?;

// Build Refresh Action Func
let refresh_action_code = "async function main(component: Input): Promise<Output> {
return { payload: JSON.parse(component.properties.resource?.payload) || { \"poop\": true } , status: \"ok\" };
}";

let fn_name = "test:refreshActionMediumLego";
let refresh_action_func = build_action_func(refresh_action_code, fn_name).await?;
let refresh_action_func = build_action_func(refresh_action_code, fn_name)?;

let update_action_code = "async function main(component: Input): Promise<Output> {
return { payload: { \"poonami\": true }, status: \"ok\" };
}";
let fn_name = "test:updateActionMediumLego";
let update_action_func = build_action_func(update_action_code, fn_name).await?;
let update_action_func = build_action_func(update_action_code, fn_name)?;

// Create Scaffold Func
let fn_name = "test:scaffoldMediumLegoAsset";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego(
}";

let fn_name = "test:createActionSmallLego";
let create_action_func = build_action_func(create_action_code, fn_name).await?;
let create_action_func = build_action_func(create_action_code, fn_name)?;

// Build Refresh Action Func
let refresh_action_code = "async function main(component: Input): Promise<Output> {
return { payload: JSON.parse(component.properties.resource?.payload) || { \"poop\": true } , status: \"ok\" };
}";

let fn_name = "test:refreshActionSmallLego";
let refresh_action_func = build_action_func(refresh_action_code, fn_name).await?;
let refresh_action_func = build_action_func(refresh_action_code, fn_name)?;

let update_action_code = "async function main(component: Input): Promise<Output> {
return { payload: { \"poonami\": true }, status: \"ok\" };
}";
let fn_name = "test:updateActionSmallLego";
let update_action_func = build_action_func(update_action_code, fn_name).await?;
let update_action_func = build_action_func(update_action_code, fn_name)?;

// Create Scaffold Func
let fn_name = "test:scaffoldSmallLegoAsset";
Expand Down Expand Up @@ -143,21 +143,21 @@ pub(crate) async fn migrate_test_exclusive_schema_small_even_lego(
}";

let fn_name = "test:createActionSmallLego";
let create_action_func = build_action_func(create_action_code, fn_name).await?;
let create_action_func = build_action_func(create_action_code, fn_name)?;

// Build Refresh Action Func
let refresh_action_code = "async function main(component: Input): Promise<Output> {
return { payload: JSON.parse(component.properties.resource?.payload) || { \"poop\": true } , status: \"ok\" };
}";

let fn_name = "test:refreshActionSmallLego";
let refresh_action_func = build_action_func(refresh_action_code, fn_name).await?;
let refresh_action_func = build_action_func(refresh_action_code, fn_name)?;

let update_action_code = "async function main(component: Input): Promise<Output> {
return { payload: { \"poonami\": true }, status: \"ok\" };
}";
let fn_name = "test:updateActionSmallLego";
let update_action_func = build_action_func(update_action_code, fn_name).await?;
let update_action_func = build_action_func(update_action_code, fn_name)?;

// Create Scaffold Func
let fn_name = "test:scaffoldSmallLegoAsset";
Expand Down
72 changes: 48 additions & 24 deletions lib/dal-test/src/schemas/test_exclusive_schema_fallout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,7 @@ pub(crate) async fn migrate_test_exclusive_schema_fallout(ctx: &DalContext) -> B

let identity_func_spec = create_identity_func()?;

let code = "async function main() {
const authCheck = requestStorage.getItem('dummySecretString');
if (authCheck) {
if (authCheck === 'todd') {
return {
status: 'ok',
payload: {
'poop': true
}
};
}
return {
status: 'error',
message: 'cannot create: dummy secret string does not match expected value'
};
} else {
return {
status: 'error',
message: 'cannot create: dummy secret string is empty'
};
}
}";
let fn_name = "test:createActionFallout";
let fallout_create_action_func = build_action_func(code, fn_name).await?;
let (fallout_create_action_func, fallout_delete_action_func) = action_funcs()?;

let fallout_scaffold_func = "function createAsset() {\
return new AssetBuilder().build();
Expand Down Expand Up @@ -179,13 +156,20 @@ pub(crate) async fn migrate_test_exclusive_schema_fallout(ctx: &DalContext) -> B
.func_unique_id(&fallout_create_action_func.unique_id)
.build()?,
)
.action_func(
ActionFuncSpec::builder()
.kind(ActionKind::Destroy)
.func_unique_id(&fallout_delete_action_func.unique_id)
.build()?,
)
.build()?,
)
.build()?;

let fallout_spec = fallout_builder
.func(identity_func_spec)
.func(fallout_create_action_func)
.func(fallout_delete_action_func)
.func(fallout_authoring_schema_func)
.func(resource_payload_to_value_func)
.schema(fallout_schema)
Expand Down Expand Up @@ -244,3 +228,43 @@ fn assemble_dummy_secret_socket_and_prop(

Ok((secret_input_socket, secret_prop))
}

fn action_funcs() -> BuiltinsResult<(FuncSpec, FuncSpec)> {
// Add the action create func.
let code = "async function main() {
const authCheck = requestStorage.getItem('dummySecretString');
if (authCheck) {
if (authCheck === 'todd') {
return {
status: 'ok',
payload: {
'poop': true
}
};
}
return {
status: 'error',
message: 'cannot create: dummy secret string does not match expected value'
};
} else {
return {
status: 'error',
message: 'cannot create: dummy secret string is empty'
};
}
}";
let fn_name = "test:createActionFallout";
let fallout_create_action_func = build_action_func(code, fn_name)?;

// Add the action delete func.
let delete_action_code = "async function main() {
return {
status: 'ok',
payload: undefined
};
}";
let fn_name = "test:deleteActionFallout";
let fallout_delete_action_func = build_action_func(delete_action_code, fn_name)?;

Ok((fallout_create_action_func, fallout_delete_action_func))
}
Loading

0 comments on commit 1609391

Please sign in to comment.