diff --git a/.env b/.sample.env similarity index 100% rename from .env rename to .sample.env diff --git a/Cargo.lock b/Cargo.lock index b8a20bf..5e912e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1000,7 +1000,7 @@ dependencies = [ [[package]] name = "detee-shared" version = "0.1.0" -source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain_app#694d5811aa0edc9b2b9bdb17c6e972b04a21b96f" +source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain_app#005677153b3fcd3251b64111a736c806106fdc04" dependencies = [ "bincode 2.0.1", "prost", diff --git a/src/db/app.rs b/src/db/app.rs index fdaac6d..3753d58 100644 --- a/src/db/app.rs +++ b/src/db/app.rs @@ -295,8 +295,6 @@ impl From for DeletedApp { disk_size_gb: value.disk_size_gb, created_at: value.created_at, price_per_unit: value.price_per_unit, - locked_nano: value.locked_nano, - collected_at: value.collected_at, mr_enclave: value.mr_enclave, package_url: value.package_url, hratls_pubkey: value.hratls_pubkey, @@ -359,15 +357,42 @@ impl ActiveApp { Ok(()) } - pub async fn delete(db: &Surreal, id: &str) -> Result { - let deleted_app: Option = db.delete((ACTIVE_APP, id)).await?; - if let Some(deleted_app) = deleted_app { - let deleted_app: DeletedApp = deleted_app.into(); - let _: Vec = db.insert(DELETED_APP).relation(deleted_app).await?; - Ok(true) - } else { - Ok(false) + pub async fn delete(db: &Surreal, admin: &str, id: &str) -> Result<(), Error> { + let mut app_del_resp = db + .query( + " + BEGIN TRANSACTION; + + LET $active_app = $app_id_input; + LET $admin = $admin_input; + + IF $active_app.in != $admin { + THROW 'Unauthorized' + }; + + return fn::delete_app($active_app); + + COMMIT TRANSACTION; + ", + ) + .bind(("app_id_input", RecordId::from((ACTIVE_APP, id)))) + .bind(("admin_input", RecordId::from((ACCOUNT, admin)))) + .await?; + + log::trace!("delete_app query response: {app_del_resp:?}"); + + let query_err = app_del_resp.take_errors(); + if !query_err.is_empty() { + log::trace!("errors in delete_app: {query_err:?}"); + let tx_fail_err_str = + String::from("The query was not executed due to a failed transaction"); + + if query_err.contains_key(&2) && query_err[&2].to_string() != tx_fail_err_str { + log::error!("Unauthorized: {}", query_err[&2]); + return Err(Error::AccessDenied); + } } + Ok(()) } } @@ -653,9 +678,17 @@ pub struct DeletedApp { pub disk_size_gb: u32, pub created_at: Datetime, pub price_per_unit: u64, - pub locked_nano: u64, - pub collected_at: Datetime, pub mr_enclave: String, pub package_url: String, pub hratls_pubkey: String, } + +impl DeletedApp { + pub async fn list_by_node(db: &Surreal, node_pubkey: &str) -> Result, Error> { + let mut result = db + .query(format!("select * from {DELETED_APP} where out = {APP_NODE}:{node_pubkey};")) + .await?; + let contracts: Vec = result.take(0)?; + Ok(contracts) + } +} diff --git a/src/db/vm.rs b/src/db/vm.rs index 28c1fb8..8dabcac 100644 --- a/src/db/vm.rs +++ b/src/db/vm.rs @@ -546,15 +546,42 @@ impl ActiveVm { Ok(false) } - pub async fn delete(db: &Surreal, id: &str) -> Result { - let deleted_vm: Option = db.delete((ACTIVE_VM, id)).await?; - if let Some(deleted_vm) = deleted_vm { - let deleted_vm: DeletedVm = deleted_vm.into(); - let _: Vec = db.insert(DELETED_VM).relation(deleted_vm).await?; - Ok(true) - } else { - Ok(false) + pub async fn delete(db: &Surreal, admin: &str, id: &str) -> Result<(), Error> { + let mut vm_del_resp = db + .query( + " + BEGIN TRANSACTION; + + LET $active_vm = $vm_id_input; + LET $admin = $admin_input; + + IF $active_vm.in != $admin { + THROW 'Unauthorized' + }; + + return fn::delete_vm($active_vm); + + COMMIT TRANSACTION; + ", + ) + .bind(("vm_id_input", RecordId::from((ACTIVE_VM, id)))) + .bind(("admin_input", RecordId::from((ACCOUNT, admin)))) + .await?; + + log::trace!("delete_vm query response: {vm_del_resp:?}"); + + let query_err = vm_del_resp.take_errors(); + if !query_err.is_empty() { + log::trace!("errors in delete_vm: {query_err:?}"); + let tx_fail_err_str = + String::from("The query was not executed due to a failed transaction"); + + if query_err.contains_key(&2) && query_err[&2].to_string() != tx_fail_err_str { + log::error!("Unauthorized: {}", query_err[&2]); + return Err(Error::AccessDenied); + } } + Ok(()) } pub async fn extend_time( diff --git a/src/grpc/app.rs b/src/grpc/app.rs index 6ab8565..0f5094c 100644 --- a/src/grpc/app.rs +++ b/src/grpc/app.rs @@ -29,7 +29,7 @@ impl AppDaemonServer { #[tonic::async_trait] impl BrainAppDaemon for AppDaemonServer { - type RegisterAppNodeStream = Pin> + Send>>; + type RegisterAppNodeStream = Pin> + Send>>; type BrainMessagesStream = Pin> + Send>>; async fn register_app_node( @@ -59,11 +59,11 @@ impl BrainAppDaemon for AppDaemonServer { app_node.register(&self.db).await?; info!("Sending existing contracts to {}", req.node_pubkey); - let contracts = db::ActiveAppWithNode::list_by_node(&self.db, &req.node_pubkey).await?; + let deleted_apps = db::DeletedApp::list_by_node(&self.db, &req.node_pubkey).await?; let (tx, rx) = mpsc::channel(6); tokio::spawn(async move { - for contract in contracts { - let _ = tx.send(Ok(contract.into())).await; + for deleted_app in deleted_apps { + let _ = tx.send(Ok(deleted_app.into())).await; } }); @@ -226,9 +226,15 @@ impl BrainAppCli for AppCliServer { async fn delete_app(&self, req: Request) -> Result, Status> { let req = check_sig_from_req(req)?; info!("delete_app process starting for {:?}", req); - match ActiveApp::delete(&self.db, &req.uuid).await? { - true => Ok(Response::new(Empty {})), - false => Err(Status::not_found(format!("Could not find App contract {}", &req.uuid))), + match ActiveApp::delete(&self.db, &req.admin_pubkey, &req.uuid).await { + Ok(()) => Ok(Response::new(Empty {})), + Err(db::Error::AccessDenied) => Err(Status::permission_denied("Unauthorized")), + Err(e) => { + log::error!("Error deleting app contract {}: {e}", &req.uuid); + Err(Status::unknown( + "Unknown error. Please try again or contact the DeTEE devs team.", + )) + } } } diff --git a/src/grpc/vm.rs b/src/grpc/vm.rs index c3f5ea3..211f97d 100644 --- a/src/grpc/vm.rs +++ b/src/grpc/vm.rs @@ -342,9 +342,15 @@ impl BrainVmCli for VmCliServer { async fn delete_vm(&self, req: Request) -> Result, Status> { let req = check_sig_from_req(req)?; - match db::ActiveVm::delete(&self.db, &req.uuid).await? { - true => Ok(Response::new(Empty {})), - false => Err(Status::not_found(format!("Could not find VM contract {}", &req.uuid))), + match db::ActiveVm::delete(&self.db, &req.admin_pubkey, &req.uuid).await { + Ok(()) => Ok(Response::new(Empty {})), + Err(db::Error::AccessDenied) => Err(Status::permission_denied("Unauthorized")), + Err(e) => { + log::error!("Error deleting VM contract {}: {e}", &req.uuid); + Err(Status::unknown( + "Unknown error. Please try again or contact the DeTEE devs team.", + )) + } } } diff --git a/surql/functions.sql b/surql/functions.sql index 062b62d..a864e5a 100644 --- a/surql/functions.sql +++ b/surql/functions.sql @@ -22,7 +22,7 @@ DEFINE FUNCTION OVERWRITE fn::delete_vm( UPDATE $account SET balance += $vm.locked_nano; }; INSERT RELATION INTO deleted_vm ( $deleted_vm ); - DELETE $vm.id; + RETURN DELETE $vm.id RETURN BEFORE; }; DEFINE FUNCTION OVERWRITE fn::app_price_per_minute( @@ -34,4 +34,21 @@ DEFINE FUNCTION OVERWRITE fn::app_price_per_minute( ($app.memory_mb / 200) + ($app.disk_size_gb / 10)) * $app.price_per_unit; +}; + +DEFINE FUNCTION OVERWRITE fn::delete_app( + $app_id: record +) { + LET $app = (select * from $app_id)[0]; + LET $account = $app.in; + LET $deleted_app = $app.patch([{ + 'op': 'replace', + 'path': 'id', + 'value': type::record("deleted_app:" + record::id($app.id)) + }]); + IF $app.locked_nano >= 0 { + UPDATE $account SET balance += $app.locked_nano; + }; + INSERT RELATION INTO deleted_app ( $deleted_app ); + RETURN DELETE $app.id RETURN BEFORE; }; \ No newline at end of file diff --git a/surql/timer.sql b/surql/timer.sql index f310afb..8d5869d 100644 --- a/surql/timer.sql +++ b/surql/timer.sql @@ -27,3 +27,5 @@ FOR $contract IN (select * from active_vm fetch out) { fn::delete_vm($contract.id); }; }; + +-- TODO: implement for active_app \ No newline at end of file diff --git a/tests/common/app_cli_utils.rs b/tests/common/app_cli_utils.rs new file mode 100644 index 0000000..d7389de --- /dev/null +++ b/tests/common/app_cli_utils.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use detee_shared::app_proto::{ + brain_app_cli_client::BrainAppCliClient, AppResource, NewAppReq, NewAppRes, +}; +use tonic::transport::Channel; + +use crate::common::test_utils::Key; + +pub async fn create_new_app( + key: &Key, + node_pubkey: &str, + brain_channel: &Channel, +) -> Result { + let new_app_req = NewAppReq { + admin_pubkey: key.pubkey.clone(), + node_pubkey: node_pubkey.to_string(), + price_per_unit: 1200, + resource: Some(AppResource { ports: vec![8080, 8081], ..Default::default() }), + locked_nano: 100, + ..Default::default() + }; + + let mut client_app_cli = BrainAppCliClient::new(brain_channel.clone()); + let new_app_resp = + client_app_cli.new_app(key.sign_request(new_app_req.clone())?).await?.into_inner(); + + assert!(new_app_resp.error.is_empty()); + assert!(new_app_resp.uuid.len() == 40); + + Ok(new_app_resp) +} diff --git a/tests/common/app_daemon_utils.rs b/tests/common/app_daemon_utils.rs index 1d4fa52..97e42a8 100644 --- a/tests/common/app_daemon_utils.rs +++ b/tests/common/app_daemon_utils.rs @@ -41,7 +41,7 @@ pub async fn register_app_node( client: &mut BrainAppDaemonClient, key: &Key, operator_wallet: &str, -) -> Result> { +) -> Result> { log::info!("Registering app_node: {}", key.pubkey); let node_pubkey = key.pubkey.clone(); @@ -133,8 +133,8 @@ pub async fn daemon_engine( }; tx.send(res).await?; } - Some(app_proto::brain_message_app::Msg::DeleteAppReq(_del_app_req)) => { - todo!() + Some(app_proto::brain_message_app::Msg::DeleteAppReq(del_app_req)) => { + println!("MOCK_APP_DAEMON:: delete app request for {}", del_app_req.uuid); } None => todo!(), } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index e5947ff..6588420 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -9,3 +9,6 @@ pub mod vm_daemon_utils; #[allow(dead_code)] pub mod app_daemon_utils; + +#[allow(dead_code)] +pub mod app_cli_utils; diff --git a/tests/common/test_utils.rs b/tests/common/test_utils.rs index 0c5b884..868b32f 100644 --- a/tests/common/test_utils.rs +++ b/tests/common/test_utils.rs @@ -1,10 +1,14 @@ use anyhow::Result; use detee_shared::app_proto as sgx_proto; +use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient; +use detee_shared::general_proto::AirdropReq; use detee_shared::vm_proto as snp_proto; use ed25519_dalek::{Signer, SigningKey}; use itertools::Itertools; use std::sync::OnceLock; +use surreal_brain::constants::TOKEN_DECIMAL; use tonic::metadata::AsciiMetadataValue; +use tonic::transport::Channel; use tonic::Request; pub static ADMIN_KEYS: OnceLock> = OnceLock::new(); @@ -83,3 +87,14 @@ impl Key { Ok(sgx_proto::DaemonAuth { timestamp, pubkey, contracts, signature }) } } + +pub async fn airdrop(brain_channel: &Channel, wallet: &str, amount: u64) -> Result<()> { + let mut client = BrainGeneralCliClient::new(brain_channel.clone()); + let airdrop_req = AirdropReq { pubkey: wallet.to_string(), tokens: amount * TOKEN_DECIMAL }; + + let admin_key = admin_keys()[0].clone(); + + client.airdrop(admin_key.sign_request(airdrop_req.clone())?).await?; + + Ok(()) +} diff --git a/tests/common/vm_cli_utils.rs b/tests/common/vm_cli_utils.rs index 7206d31..52116c4 100644 --- a/tests/common/vm_cli_utils.rs +++ b/tests/common/vm_cli_utils.rs @@ -1,29 +1,18 @@ -use super::test_utils::{admin_keys, Key}; +use super::test_utils::Key; use anyhow::{anyhow, Result}; use detee_shared::app_proto; use detee_shared::common_proto::Empty; use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient; -use detee_shared::general_proto::{Account, AirdropReq, RegOperatorReq, ReportNodeReq}; +use detee_shared::general_proto::{Account, RegOperatorReq, ReportNodeReq}; use detee_shared::vm_proto; use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; use futures::StreamExt; -use surreal_brain::constants::{ACTIVE_VM, NEW_VM_REQ, TOKEN_DECIMAL}; +use surreal_brain::constants::{ACTIVE_VM, NEW_VM_REQ}; use surreal_brain::db::prelude as db; use surrealdb::engine::remote::ws::Client; use surrealdb::Surreal; use tonic::transport::Channel; -pub async fn airdrop(brain_channel: &Channel, wallet: &str, amount: u64) -> Result<()> { - let mut client = BrainGeneralCliClient::new(brain_channel.clone()); - let airdrop_req = AirdropReq { pubkey: wallet.to_string(), tokens: amount * TOKEN_DECIMAL }; - - let admin_key = admin_keys()[0].clone(); - - client.airdrop(admin_key.sign_request(airdrop_req.clone())?).await?; - - Ok(()) -} - pub async fn create_new_vm( db: &Surreal, key: &Key, diff --git a/tests/common/vm_daemon_utils.rs b/tests/common/vm_daemon_utils.rs index 96abacc..8af4f00 100644 --- a/tests/common/vm_daemon_utils.rs +++ b/tests/common/vm_daemon_utils.rs @@ -136,8 +136,8 @@ pub async fn daemon_engine( Some(vm_proto::brain_vm_message::Msg::UpdateVmReq(_update_vm_req)) => { todo!() } - Some(vm_proto::brain_vm_message::Msg::DeleteVm(_del_vm_req)) => { - todo!() + Some(vm_proto::brain_vm_message::Msg::DeleteVm(del_vm_req)) => { + println!("MOCK_VM_DAEMON:: delete vm request for {}", del_vm_req.uuid); } None => todo!(), } diff --git a/tests/grpc_app_cli_test.rs b/tests/grpc_app_cli_test.rs index 574f3b3..1c21b28 100644 --- a/tests/grpc_app_cli_test.rs +++ b/tests/grpc_app_cli_test.rs @@ -1,13 +1,14 @@ use common::app_daemon_utils::mock_app_daemon; use common::prepare_test_env::{prepare_test_db, run_service_for_stream}; -use common::test_utils::Key; -use common::vm_cli_utils::airdrop; -use detee_shared::app_proto; +use common::test_utils::{airdrop, Key}; use detee_shared::app_proto::brain_app_cli_client::BrainAppCliClient; +use detee_shared::app_proto::{self, DelAppReq}; use std::vec; -use surreal_brain::constants::{ACCOUNT, ACTIVE_APP, NEW_APP_REQ, TOKEN_DECIMAL}; +use surreal_brain::constants::{ACCOUNT, ACTIVE_APP, DELETED_APP, NEW_APP_REQ, TOKEN_DECIMAL}; use surreal_brain::db::prelude as db; +use crate::common::app_cli_utils::create_new_app; + mod common; #[tokio::test] @@ -89,3 +90,93 @@ async fn test_app_creation() { assert_eq!(acc_db.balance, airdrop_amount * TOKEN_DECIMAL - (locking_nano + 100)); assert_eq!(acc_db.tmp_locked, 0); } + +#[tokio::test] +async fn test_timeout_app_creation() { + let _ = prepare_test_db().await.unwrap(); + let brain_channel = run_service_for_stream().await.unwrap(); + let daemon_key = Key::new().pubkey.clone(); + + let key = Key::new(); + + let new_app_req = app_proto::NewAppReq { + admin_pubkey: key.pubkey.clone(), + node_pubkey: daemon_key.clone(), + price_per_unit: 1200, + resource: Some(app_proto::AppResource { ports: vec![8080, 8081], ..Default::default() }), + locked_nano: 100, + ..Default::default() + }; + + airdrop(&brain_channel, &key.pubkey, 10).await.unwrap(); + + let mut client_app_cli = BrainAppCliClient::new(brain_channel.clone()); + let timeout_resp = client_app_cli.new_app(key.sign_request(new_app_req.clone()).unwrap()).await; + assert!(timeout_resp.is_err()); + let timeout_err = timeout_resp.err().unwrap(); + assert_eq!( + timeout_err.message(), + "Network timeout. Please try again later or contact the DeTEE devs team." + ); +} + +#[tokio::test] +async fn test_app_deletion() { + let db = prepare_test_db().await.unwrap(); + let brain_channel = run_service_for_stream().await.unwrap(); + let daemon_key = mock_app_daemon(&brain_channel, None).await.unwrap(); + + let key = Key::new(); + airdrop(&brain_channel, &key.pubkey, 10).await.unwrap(); + + let new_app_res = create_new_app(&key, &daemon_key, &brain_channel).await.unwrap(); + + let mut client_app_cli = BrainAppCliClient::new(brain_channel.clone()); + + let del_app_req = DelAppReq { admin_pubkey: key.pubkey.clone(), uuid: new_app_res.uuid }; + let _ = client_app_cli.delete_app(key.sign_request(del_app_req).unwrap()).await.unwrap(); + + let key_02 = Key::new(); + + // delete random app + let mut del_app_req = DelAppReq { + admin_pubkey: key_02.pubkey.clone(), + uuid: "9ae3VH8nJg2i8pqTQ6mJtvYuS2kd9n1XLLco8GUPfT95".to_string(), + }; + + let del_err = client_app_cli + .delete_app(key_02.sign_request(del_app_req.clone()).unwrap()) + .await + .err() + .unwrap(); + assert_eq!(del_err.message(), "Unauthorized"); + + let new_app_res_02 = create_new_app(&key, &daemon_key, &brain_channel).await.unwrap(); + del_app_req.uuid = new_app_res_02.uuid; + + let del_err = client_app_cli + .delete_app(key_02.sign_request(del_app_req.clone()).unwrap()) + .await + .err() + .unwrap(); + assert_eq!(del_err.message(), "Unauthorized"); + + // test refund + let key_03 = Key::new(); + airdrop(&brain_channel, &key_03.pubkey, 10).await.unwrap(); + + let new_app_res_03 = create_new_app(&key_03, &daemon_key, &brain_channel).await.unwrap(); + + let del_app_req = + DelAppReq { admin_pubkey: key_03.pubkey.clone(), uuid: new_app_res_03.uuid.clone() }; + let _ = client_app_cli.delete_app(key_03.sign_request(del_app_req).unwrap()).await.unwrap(); + + let acc: db::Account = db.select((ACCOUNT, key_03.pubkey.clone())).await.unwrap().unwrap(); + assert_eq!(acc.balance, 10 * TOKEN_DECIMAL); + + let deleted_app = + db.select::>((DELETED_APP, new_app_res_03.uuid)).await.unwrap(); + assert!(deleted_app.is_some()); +} + +// TODO: test register app node, delete app contract while node offline, kick, etc.. diff --git a/tests/grpc_general_test.rs b/tests/grpc_general_test.rs index fc0c096..f115392 100644 --- a/tests/grpc_general_test.rs +++ b/tests/grpc_general_test.rs @@ -1,10 +1,10 @@ use common::prepare_test_env::{ prepare_test_db, run_service_for_stream, run_service_in_background, }; -use common::test_utils::{admin_keys, Key}; +use common::test_utils::{admin_keys, airdrop, Key}; use common::vm_cli_utils::{ - airdrop, create_new_vm, list_accounts, list_all_app_contracts, list_all_vm_contracts, - register_operator, report_node, + create_new_vm, list_accounts, list_all_app_contracts, list_all_vm_contracts, register_operator, + report_node, }; use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; use detee_shared::common_proto::{Empty, Pubkey}; diff --git a/tests/grpc_vm_cli_test.rs b/tests/grpc_vm_cli_test.rs index 5d68ce1..dd5ebf6 100644 --- a/tests/grpc_vm_cli_test.rs +++ b/tests/grpc_vm_cli_test.rs @@ -1,14 +1,14 @@ use common::prepare_test_env::{prepare_test_db, run_service_for_stream}; -use common::test_utils::Key; -use common::vm_cli_utils::{airdrop, create_new_vm, user_list_vm_contracts}; +use common::test_utils::{airdrop, Key}; +use common::vm_cli_utils::{create_new_vm, user_list_vm_contracts}; use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; -use detee_shared::vm_proto; use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient; +use detee_shared::vm_proto::{self, DeleteVmReq}; use detee_shared::vm_proto::{ExtendVmReq, ListVmContractsReq, NewVmReq}; use futures::StreamExt; use std::vec; -use surreal_brain::constants::{ACCOUNT, ACTIVE_VM, NEW_VM_REQ, TOKEN_DECIMAL}; +use surreal_brain::constants::{ACCOUNT, ACTIVE_VM, DELETED_VM, NEW_VM_REQ, TOKEN_DECIMAL}; use surreal_brain::db::prelude as db; mod common; @@ -125,6 +125,65 @@ async fn test_timeout_vm_creation() { ) } +#[tokio::test] +async fn test_vm_deletion() { + let db = prepare_test_db().await.unwrap(); + let brain_channel = run_service_for_stream().await.unwrap(); + let daemon_key = mock_vm_daemon(&brain_channel, None).await.unwrap(); + + let key = Key::new(); + airdrop(&brain_channel, &key.pubkey, 10).await.unwrap(); + + let new_vm_uuid = create_new_vm(&db, &key, &daemon_key, &brain_channel).await.unwrap(); + + let mut client_vm_cli = BrainVmCliClient::new(brain_channel.clone()); + + let del_app_req = DeleteVmReq { admin_pubkey: key.pubkey.clone(), uuid: new_vm_uuid }; + let _ = client_vm_cli.delete_vm(key.sign_request(del_app_req).unwrap()).await.unwrap(); + + let key_02 = Key::new(); + + // delete random vm + let mut del_vm_req = DeleteVmReq { + admin_pubkey: key_02.pubkey.clone(), + uuid: "9ae3VH8nJg2i8pqTQ6mJtvYuS2kd9n1XLLco8GUPfT95".to_string(), + }; + + let del_err = client_vm_cli + .delete_vm(key_02.sign_request(del_vm_req.clone()).unwrap()) + .await + .err() + .unwrap(); + assert_eq!(del_err.message(), "Unauthorized"); + + let new_vm_uuid_02 = create_new_vm(&db, &key, &daemon_key, &brain_channel).await.unwrap(); + del_vm_req.uuid = new_vm_uuid_02; + + let del_err = client_vm_cli + .delete_vm(key_02.sign_request(del_vm_req.clone()).unwrap()) + .await + .err() + .unwrap(); + assert_eq!(del_err.message(), "Unauthorized"); + + // test refund + let key_03 = Key::new(); + airdrop(&brain_channel, &key_03.pubkey, 10).await.unwrap(); + + let new_vm_uuid_03 = create_new_vm(&db, &key_03, &daemon_key, &brain_channel).await.unwrap(); + + let del_vm_req = + DeleteVmReq { admin_pubkey: key_03.pubkey.clone(), uuid: new_vm_uuid_03.clone() }; + let _ = client_vm_cli.delete_vm(key_03.sign_request(del_vm_req).unwrap()).await.unwrap(); + + let acc: db::Account = db.select((ACCOUNT, key_03.pubkey.clone())).await.unwrap().unwrap(); + assert_eq!(acc.balance, 10 * TOKEN_DECIMAL); + + let deleted_vm = + db.select::>((DELETED_VM, new_vm_uuid_03)).await.unwrap(); + assert!(deleted_vm.is_some()); +} + #[tokio::test] // TODO: create vm for this user before testing this async fn test_list_vm_contracts() { @@ -240,3 +299,5 @@ async fn test_extend_vm() { let acc: db::Account = db_conn.select((ACCOUNT, key.pubkey.clone())).await.unwrap().unwrap(); assert_eq!(acc.balance, expected_bal_02); } + +// TODO: test register vm node, delete vm contract while node offline, kick, etc.. diff --git a/tests/grpc_vm_daemon_test.rs b/tests/grpc_vm_daemon_test.rs index 5a684e2..2e47c42 100644 --- a/tests/grpc_vm_daemon_test.rs +++ b/tests/grpc_vm_daemon_test.rs @@ -1,8 +1,7 @@ use common::prepare_test_env::{ prepare_test_db, run_service_for_stream, run_service_in_background, }; -use common::test_utils::Key; -use common::vm_cli_utils::airdrop; +use common::test_utils::{airdrop, Key}; use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; use detee_shared::vm_proto; use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient;