Skip to content

Commit

Permalink
merge: #3947
Browse files Browse the repository at this point in the history
3947: Do not delete frames if dependent components have resources r=nickgerace a=nickgerace

## Description

This PR ensures that we do not delete frames if dependent components have resources.

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

<img src="https://media4.giphy.com/media/UG86K2Qxx7qRa/giphy.gif"/>

## 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



Co-authored-by: Brit Myers <[email protected]>
  • Loading branch information
si-bors-ng[bot] and britmyerss authored Jun 6, 2024
2 parents cceb6ea + 1609391 commit 96aa0b9
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 96aa0b9

Please sign in to comment.