From cbd71b2308e04943d6409e9ffe984478f66318f0 Mon Sep 17 00:00:00 2001 From: Noor Date: Wed, 4 Jun 2025 21:04:32 +0530 Subject: [PATCH] Implement refund on delete app and vm fix authentication and refund on delete vm refactor app daemon register to get delete app req db function for delete app sample environment variable extensive tests for delete app and vm refactor test utilities --- .env => .sample.env | 0 Cargo.lock | 2 +- src/db/app.rs | 57 ++++++++++++++---- src/db/vm.rs | 43 +++++++++++--- src/grpc/app.rs | 20 ++++--- src/grpc/vm.rs | 12 +++- surql/functions.sql | 19 +++++- surql/timer.sql | 2 + tests/common/app_cli_utils.rs | 31 ++++++++++ tests/common/app_daemon_utils.rs | 6 +- tests/common/mod.rs | 3 + tests/common/test_utils.rs | 15 +++++ tests/common/vm_cli_utils.rs | 17 +----- tests/common/vm_daemon_utils.rs | 4 +- tests/grpc_app_cli_test.rs | 99 ++++++++++++++++++++++++++++++-- tests/grpc_general_test.rs | 6 +- tests/grpc_vm_cli_test.rs | 69 ++++++++++++++++++++-- tests/grpc_vm_daemon_test.rs | 3 +- 18 files changed, 344 insertions(+), 64 deletions(-) rename .env => .sample.env (100%) create mode 100644 tests/common/app_cli_utils.rs 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;