/* * AquaVM Workflow Engine * * Copyright (C) 2024 Fluence DAO * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation version 3 of the * License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use air::no_error_object; use air::CatchableError; use air::ErrorObjectError; use air::ExecutionCidState; use air::ExecutionError; use air::LambdaError; use air::SecurityTetraplet; use air::NO_ERROR_ERROR_CODE; use air::NO_ERROR_MESSAGE; use air_test_framework::AirScriptExecutor; use air_test_utils::prelude::*; use futures::FutureExt; use std::cell::RefCell; use std::rc::Rc; type ArgToCheck = Rc>>; fn create_check_service_closure( args_to_check: ArgToCheck, tetraplets_to_check: ArgToCheck>>, ) -> CallServiceClosure<'static> { Box::new(move |params| { let args_to_check = args_to_check.clone(); let tetraplets_to_check = tetraplets_to_check.clone(); async move { let mut call_args: Vec = params.arguments; let result = json!(params.tetraplets); *args_to_check.borrow_mut() = Some(call_args.remove(0)); *tetraplets_to_check.borrow_mut() = Some(params.tetraplets); CallServiceResult::ok(result) } .boxed_local() }) } #[tokio::test] async fn last_error_tetraplets() { let set_variable_peer_id = "set_variable"; let mut set_variable_vm = create_avm(unit_call_service(), set_variable_peer_id).await; let fallible_peer_id = "fallible_peer_id"; let mut fallible_vm = create_avm(fallible_call_service("fallible_call_service"), fallible_peer_id).await; let local_peer_id = "local_peer_id"; let args = Rc::new(RefCell::new(None)); let tetraplets = Rc::new(RefCell::new(None)); let mut local_vm = create_avm( create_check_service_closure(args.clone(), tetraplets.clone()), local_peer_id, ) .await; let script = format!( include_str!("scripts/create_service_with_xor.air"), set_variable_peer_id, fallible_peer_id, local_peer_id ); let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", ""); let result = checked_call_vm!(fallible_vm, <_>::default(), &script, "", result.data); let _ = checked_call_vm!(local_vm, <_>::default(), script, "", result.data); let actual_value = (*args.borrow()).as_ref().unwrap().clone(); let last_error = actual_value.as_object().unwrap(); assert_eq!( last_error.get("instruction").unwrap(), r#"call "fallible_peer_id" ("fallible_call_service" "") [service_id] client_result"# ); assert_eq!( last_error.get("message").unwrap(), r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"# ); let tetraplet = (*tetraplets.borrow()).as_ref().unwrap()[0][0].clone(); assert_eq!(tetraplet.peer_pk, fallible_peer_id); assert_eq!(tetraplet.service_id, "fallible_call_service"); assert_eq!(tetraplet.function_name, ""); assert_eq!(&(*tetraplets.borrow()).as_ref().unwrap()[0][0].lens, ""); } #[tokio::test] async fn not_clear_last_error_in_match() { let set_variable_peer_id = "set_variable"; let mut set_variable_vm = create_avm(unit_call_service(), set_variable_peer_id).await; let local_peer_id = "local_peer_id"; let args = Rc::new(RefCell::new(None)); let tetraplets = Rc::new(RefCell::new(None)); let mut local_vm = create_avm(create_check_service_closure(args.clone(), tetraplets), local_peer_id).await; let script = format!( r#" (seq (call "{set_variable_peer_id}" ("" "") [] relayVariableName) (xor (match relayVariableName "" (call "unknown_peer" ("" "") [%last_error%]) ) (seq (call "{local_peer_id}" ("" "") [%last_error%]) (null) ) ) ) "# ); let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", ""); let _ = checked_call_vm!(local_vm, <_>::default(), &script, "", result.data); let actual_value: JValue = (*args.borrow()).as_ref().unwrap().clone().into(); assert_eq!(actual_value, no_error_object()); } #[tokio::test] async fn not_clear_last_error_in_mismatch() { let set_variable_peer_id = "set_variable"; let mut set_variable_vm = create_avm(unit_call_service(), set_variable_peer_id).await; let local_peer_id = "local_peer_id"; let args = Rc::new(RefCell::new(None)); let tetraplets = Rc::new(RefCell::new(None)); let mut local_vm = create_avm(create_check_service_closure(args.clone(), tetraplets), local_peer_id).await; let script = format!( r#" (seq (call "{set_variable_peer_id}" ("" "") [] relayVariableName) (xor (mismatch relayVariableName "result from unit_call_service" (call "unknown_peer" ("" "") [%last_error%]) ) (seq (null) (call "{local_peer_id}" ("" "") [%last_error%]) ) ) ) "# ); let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", ""); let _ = checked_call_vm!(local_vm, <_>::default(), &script, "", result.data); let actual_value: JValue = (*args.borrow()).as_ref().unwrap().into(); assert_eq!(actual_value, no_error_object()); } #[tokio::test] async fn track_current_peer_id() { let fallible_peer_id = "fallible_peer_id"; let mut fallible_vm = create_avm(fallible_call_service("fallible_call_service"), fallible_peer_id).await; let local_peer_id = "local_peer_id"; let args = Rc::new(RefCell::new(None)); let tetraplets = Rc::new(RefCell::new(None)); let mut local_vm = create_avm(create_check_service_closure(args.clone(), tetraplets), local_peer_id).await; let script = format!( r#" (xor (call "{fallible_peer_id}" ("fallible_call_service" "") [""]) (call "{local_peer_id}" ("" "") [%last_error%]) ) "# ); let result = checked_call_vm!(fallible_vm, <_>::default(), &script, "", ""); let _ = checked_call_vm!(local_vm, <_>::default(), script, "", result.data); let actual_value: JValue = (*args.borrow()).as_ref().unwrap().into(); let last_error = actual_value.as_object().unwrap(); assert_eq!(last_error.get("peer_id").unwrap(), fallible_peer_id); } #[tokio::test] async fn variable_names_shown_in_error() { let set_variable_vm_peer_id = "set_variable_vm_peer_id"; let mut set_variable_vm = create_avm(set_variable_call_service(json!(1u32)), set_variable_vm_peer_id).await; let echo_vm_peer_id = "echo_vm_peer_id"; let mut echo_vm = create_avm(echo_call_service(), echo_vm_peer_id).await; let script = format!( r#" (xor (seq (call "{set_variable_vm_peer_id}" ("" "") [""] -relay-) (call -relay- ("" "") []) ) (call "{echo_vm_peer_id}" ("" "") [%last_error%.$.message]) ) "# ); let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", ""); let result = checked_call_vm!(echo_vm, <_>::default(), script, "", result.data); let trace = trace_from_result(&result); let msg = "call cannot resolve non-String triplet variable part `-relay-` with value '1'"; assert_eq!(trace[1.into()], unused!(msg, peer = echo_vm_peer_id, args = vec![msg])); } #[tokio::test] async fn non_initialized_last_error() { let vm_peer_id = "vm_peer_id"; let args = Rc::new(RefCell::new(None)); let tetraplets = Rc::new(RefCell::new(None)); let mut vm = create_avm( create_check_service_closure(args.clone(), tetraplets.clone()), vm_peer_id, ) .await; let script = format!( r#" (seq (call "{vm_peer_id}" ("" "") [%last_error%]) (null) ) "# ); let test_params = TestRunParameters::from_init_peer_id("init_peer_id"); let _ = checked_call_vm!(vm, test_params.clone(), script, "", ""); let actual_value: JValue = (*args.borrow()).as_ref().unwrap().into(); assert_eq!(actual_value, no_error_object(),); let actual_tetraplets = (*tetraplets.borrow()).as_ref().unwrap().clone(); assert_eq!( actual_tetraplets, vec![vec![SecurityTetraplet::new(test_params.init_peer_id, "", "", "")]] ); } #[tokio::test] async fn access_last_error_by_not_exists_field() { let fallible_peer_id = "fallible_peer_id"; let mut fallible_vm = create_avm(fallible_call_service("fallible_call_service"), fallible_peer_id).await; let local_peer_id = "local_peer_id"; let non_exists_field_name = "non_exists_field"; let script = format!( r#" (xor (call "{fallible_peer_id}" ("fallible_call_service" "") [""]) (call "{local_peer_id}" ("" "") [%last_error%.$.{non_exists_field_name}]) ) "# ); let result = call_vm!(fallible_vm, <_>::default(), &script, "", ""); let expected_error = ExecutionError::Catchable(rc!(CatchableError::LambdaApplierError( LambdaError::ValueNotContainSuchField { value: json!({ "error_code": 10000i64, "instruction": r#"call "fallible_peer_id" ("fallible_call_service" "") [""] "#, "message": r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#, "peer_id": "fallible_peer_id", }).into(), field_name: non_exists_field_name.to_string() } ))); assert!(check_error(&result, expected_error)); } #[tokio::test] async fn last_error_with_par_one_subgraph_failed() { let fallible_peer_id = "fallible_peer_id"; let fallible_call_service_name = "fallible_call_service"; let mut fallible_vm = create_avm(fallible_call_service(fallible_call_service_name), fallible_peer_id).await; let vm_peer_id = "local_peer_id"; let args = Rc::new(RefCell::new(None)); let tetraplets = Rc::new(RefCell::new(None)); let mut vm = create_avm(create_check_service_closure(args.clone(), tetraplets), vm_peer_id).await; let script = format!( r#" (seq (par (call "{fallible_peer_id}" ("{fallible_call_service_name}" "") [""]) (call "{fallible_peer_id}" ("non_fallible_call_service" "") [""]) ) (call "{vm_peer_id}" ("" "") [%last_error%]) ) "# ); let result = checked_call_vm!(fallible_vm, <_>::default(), &script, "", ""); let _ = checked_call_vm!(vm, <_>::default(), script, "", result.data); let actual_value = (*args.borrow()).as_ref().unwrap().clone(); let expected_value = json!({ "error_code": 10000i64, "instruction": r#"call "fallible_peer_id" ("fallible_call_service" "") [""] "#, "message": r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#, "peer_id": fallible_peer_id }); assert_eq!(actual_value, expected_value); } #[tokio::test] async fn fail_with_scalar_rebubble_error() { let fallible_peer_id = "fallible_peer_id"; let mut fallible_vm = create_avm(fallible_call_service("fallible_call_service"), fallible_peer_id).await; let script = format!( r#" (xor (call "{fallible_peer_id}" ("fallible_call_service" "") [""]) (seq (ap %last_error% scalar) (fail scalar) ) ) "# ); let result = call_vm!(fallible_vm, <_>::default(), &script, "", ""); let expected_error = CatchableError::UserError { error: json!({ "error_code": 10000i64, "instruction": r#"call "fallible_peer_id" ("fallible_call_service" "") [""] "#, "message": r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#, "peer_id": "fallible_peer_id", }).into(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn fail_with_scalar_from_call() { let vm_peer_id = "vm_peer_id"; let error_code = 1337; let error_message = "error message"; let service_result = json!({"error_code": error_code, "message": error_message}); let mut vm = create_avm(set_variable_call_service(service_result), vm_peer_id).await; let script = format!( r#" (seq (call "{vm_peer_id}" ("" "") [""] scalar) (fail scalar) ) "# ); let result = call_vm!(vm, <_>::default(), &script, "", ""); let expected_error = CatchableError::UserError { error: json!({ "error_code": error_code, "message": error_message, }) .into(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn fail_with_scalar_with_lambda_from_call() { let vm_peer_id = "vm_peer_id"; let error_code = 1337; let error_message = "error message"; let service_result = json!({"error": {"error_code": error_code, "message": error_message}}); let mut vm = create_avm(set_variable_call_service(service_result), vm_peer_id).await; let script = format!( r#" (seq (call "{vm_peer_id}" ("" "") [""] scalar) (fail scalar.$.error) ) "# ); let result = call_vm!(vm, <_>::default(), &script, "", ""); let expected_error = CatchableError::UserError { error: json!({ "error_code": error_code, "message": error_message, }) .into(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn fail_with_scalar_from_call_not_enough_fields() { let vm_peer_id = "vm_peer_id"; let error_code = 1337; let service_result = json!({ "error_code": error_code }); let mut vm = create_avm(set_variable_call_service(service_result.clone()), vm_peer_id).await; let script = format!( r#" (seq (call "{vm_peer_id}" ("" "") [""] scalar) (fail scalar) ) "# ); let result = call_vm!(vm, <_>::default(), &script, "", ""); let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustContainField { scalar: service_result.into(), field_name: "message", }); assert!(check_error(&result, expected_error)); } #[tokio::test] async fn fail_with_scalar_from_call_not_right_type() { let vm_peer_id = "vm_peer_id"; let service_result = json!([]); let mut vm = create_avm(set_variable_call_service(service_result.clone()), vm_peer_id).await; let script = format!( r#" (seq (call "{vm_peer_id}" ("" "") [""] scalar) (fail scalar) ) "# ); let result = call_vm!(vm, <_>::default(), &script, "", ""); let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustBeObject(service_result.into())); assert!(check_error(&result, expected_error)); } #[tokio::test] async fn fail_with_scalar_from_call_field_not_right_type() { let vm_peer_id = "vm_peer_id"; let service_result = json!({"error_code": "error_code", "message": "error message"}); let mut vm = create_avm(set_variable_call_service(service_result.clone()), vm_peer_id).await; let script = format!( r#" (seq (call "{vm_peer_id}" ("" "") [""] scalar) (fail scalar) ) "# ); let result = call_vm!(vm, <_>::default(), &script, "", ""); let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarFieldIsWrongType { scalar: service_result.into(), field_name: "error_code", expected_type: "integer", }); assert!(check_error(&result, expected_error)); } #[tokio::test] async fn last_error_with_match() { let vm_peer_id = "vm_peer_id"; let mut vm = create_avm(fallible_call_service("fallible_call_service"), vm_peer_id).await; let script = format!( r#" (xor (call "{vm_peer_id}" ("fallible_call_service" "") [""]) (match %last_error%.$.error_code 10000 (call "{vm_peer_id}" ("" "") [%last_error%]) ) ) "# ); let result = checked_call_vm!(vm, <_>::default(), &script, "", ""); let trace = trace_from_result(&result); assert_eq!(trace.len(), 2); // if match works there will be 2 calls in a resulted trace } #[tokio::test] async fn undefined_last_error_errcode() { let local_peer_id = "local_peer_id"; let script = format!( r#" (call "{local_peer_id}" ("test" "error_code") [%last_error%.$.error_code] scalar) ; behaviour = echo "# ); let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(local_peer_id), &script) .await .expect("invalid test AIR script"); let result = executor.execute_all(local_peer_id).await.unwrap(); let actual_trace = trace_from_result(&result.last().unwrap()); let mut cid_state = ExecutionCidState::new(); let errcode_lambda_output = json!(NO_ERROR_ERROR_CODE); let expected_trace = ExecutionTrace::from(vec![scalar_tracked!( errcode_lambda_output.clone(), cid_state, peer_name = local_peer_id, service = "test..0", function = "error_code", args = vec![errcode_lambda_output] )]); assert_eq!(actual_trace, expected_trace); } #[tokio::test] async fn undefined_last_error_msg_errcode() { let local_peer_id = "local_peer_id"; let script = format!( r#" (call "{local_peer_id}" ("test" "message") [%last_error%.$.message] scalar1) ; behaviour = echo "# ); let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(local_peer_id), &script) .await .expect("invalid test AIR script"); let result = executor.execute_all(local_peer_id).await.unwrap(); let actual_trace = trace_from_result(&result.last().unwrap()); let mut cid_state = ExecutionCidState::new(); let message_lambda_output = json!(NO_ERROR_MESSAGE); let expected_trace = ExecutionTrace::from(vec![scalar_tracked!( message_lambda_output.clone(), cid_state, peer_name = local_peer_id, service = "test..0", function = "message", args = vec![message_lambda_output] )]); assert_eq!(actual_trace, expected_trace); }