From 3e7981da912f78e51943c75df6bbc3e58345cb66 Mon Sep 17 00:00:00 2001 From: Alexis Langlet Date: Fri, 24 Nov 2023 12:51:05 +0100 Subject: [PATCH] test(api): test run_code in mod --- api/src/api/mod.rs | 127 +++++++++++++++++++++++++++++++++-------- api/src/api/service.rs | 71 +++++++++++++---------- api/src/main.rs | 14 +++-- 3 files changed, 154 insertions(+), 58 deletions(-) diff --git a/api/src/api/mod.rs b/api/src/api/mod.rs index ff1cca2..4e3d19c 100644 --- a/api/src/api/mod.rs +++ b/api/src/api/mod.rs @@ -4,40 +4,29 @@ use actix_web::{post, web, Responder}; use log::{debug, error, info, trace, warn}; use crate::{ - api::service::LambdoApiService, + api::service::{LambdoApiService, LambdoApiServiceTrait}, model::{RunRequest, RunResponse}, vm_manager::{self, grpc_definitions::ExecuteResponse}, }; use std::error::Error; -#[post("/run")] -async fn run( - run_body: web::Json, - service: web::Data, -) -> Result> { - debug!( - "Received code execution request from http (language: {}, version: {})", - run_body.language, run_body.version - ); - trace!("Request body: {:?}", run_body); +async fn run_code(run_resquest: RunRequest, service: &dyn LambdoApiServiceTrait) -> RunResponse { + let response = service.run_code(run_resquest).await; - let response = service.run_code(run_body.into_inner()).await; - - let response = match response { + match response { Ok(response) => { info!("Execution ended for {:?}", response.id); trace!("Response: {:?}", response); parse_response(response) } - // for the moment just signal an internal server error Err(e) => match e { vm_manager::Error::Timeout => { warn!("Timeout while executing code"); - return Ok(web::Json(RunResponse { + RunResponse { status: 128, stdout: "".to_string(), stderr: "Timeout".to_string(), - })); + } } _ => { error!("Error while executing code: {:?}", e); @@ -48,12 +37,35 @@ async fn run( } } }, - }; + } +} + +#[post("/run")] +pub async fn post_run_route( + run_body: web::Json, + api_service: web::Data, +) -> Result> { + debug!( + "Received code execution request from http (language: {}, version: {})", + run_body.language, run_body.version + ); + trace!("Request body: {:?}", run_body); + + let service = api_service.get_ref(); + let result = run_code(run_body.into_inner(), service); - Ok(web::Json(response)) + Ok(web::Json(result.await)) } fn parse_response(response: ExecuteResponse) -> RunResponse { + if response.steps.is_empty() { + return RunResponse { + status: 1, + stdout: "".to_string(), + stderr: "Nothing was run".to_string(), + }; + } + let mut stdout = String::new(); let mut stderr = String::new(); for step in response.steps.as_slice() { @@ -67,16 +79,23 @@ fn parse_response(response: ExecuteResponse) -> RunResponse { status: response.steps[response.steps.len() - 1] .exit_code .try_into() - .unwrap(), + .unwrap_or(1), stdout, stderr, } } #[cfg(test)] -mod test{ - use crate::{vm_manager::grpc_definitions::{ExecuteResponse, ExecuteResponseStep}, api::parse_response}; +mod test { + use std::vec; + + use crate::{ + api::{parse_response, run_code}, + model::RunRequest, + vm_manager::grpc_definitions::{ExecuteResponse, ExecuteResponseStep, FileModel}, + }; + use super::service::MockLambdoApiServiceTrait; #[test] fn test_parse_response_stdout() { @@ -131,4 +150,66 @@ mod test{ assert_eq!(parsed.stderr, "Error"); assert_eq!(parsed.status, 1); } -} \ No newline at end of file + + #[tokio::test] + async fn test_run_code_with_no_steps() { + let mut mock_service = MockLambdoApiServiceTrait::new(); + mock_service.expect_run_code().once().returning(|_| { + Ok(ExecuteResponse { + id: "test".to_string(), + steps: vec![], + }) + }); + + let run_request = RunRequest { + language: "Node".to_string(), + version: "1".to_string(), + code: vec![], + input: "".to_string(), + }; + + let response = run_code(run_request, &mock_service).await; + assert_eq!(response.status, 1); + assert_eq!(response.stdout, ""); + assert_eq!(response.stderr, "Nothing was run"); + } + + #[tokio::test] + async fn test_run_with_steps() { + let mut mock_service = MockLambdoApiServiceTrait::new(); + mock_service.expect_run_code().once().returning(|_| { + Ok(ExecuteResponse { + id: "test".to_string(), + steps: vec![ + ExecuteResponseStep { + command: "echo Hello".to_string(), + stdout: "Hello".to_string(), + stderr: "".to_string(), + exit_code: 0, + }, + ExecuteResponseStep { + command: "echo World".to_string(), + stdout: "World".to_string(), + stderr: "".to_string(), + exit_code: 0, + }, + ], + }) + }); + + let run_request = RunRequest { + language: "Node".to_string(), + version: "1".to_string(), + code: vec![FileModel { + filename: "test.js".to_string(), + content: "console.log('Hello World')".to_string(), + }], + input: "test.js".to_string(), + }; + + let response = run_code(run_request, &mock_service).await; + assert_eq!(response.status, 0); + assert_eq!(response.stdout, "HelloWorld"); + assert_eq!(response.stderr, ""); + } +} diff --git a/api/src/api/service.rs b/api/src/api/service.rs index d4f5d5b..429b2f6 100644 --- a/api/src/api/service.rs +++ b/api/src/api/service.rs @@ -7,10 +7,17 @@ use crate::{ vm_manager::{state::LambdoStateRef, Error, VMManager}, }; use log::{debug, trace}; +use mockall::automock; use uuid::Uuid; use crate::model::RunRequest; +#[automock] +#[async_trait::async_trait] +pub trait LambdoApiServiceTrait: Send + Sync { + async fn run_code(&self, request: RunRequest) -> Result; +} + pub struct LambdoApiService { pub config: LambdoConfig, pub vm_manager: Box, @@ -36,7 +43,39 @@ impl LambdoApiService { }) } - pub async fn run_code(&self, request: RunRequest) -> Result { + fn find_language( + &self, + language: &String, + ) -> Result> { + let language_list = &self.config.languages; + for lang in language_list { + if &*lang.name == language { + return Ok(lang.clone()); + } + } + Err("Language not found".into()) + } + + fn generate_steps( + language_settings: &LambdoLanguageConfig, + entrypoint: &str, + ) -> Vec { + let mut steps: Vec = Vec::new(); + for step in &language_settings.steps { + let command = step.command.replace("{{filename}}", entrypoint); + + steps.push(ExecuteRequestStep { + command, + enable_output: step.output.enabled, + }); + } + steps + } +} + +#[async_trait::async_trait] +impl LambdoApiServiceTrait for LambdoApiService { + async fn run_code(&self, request: RunRequest) -> Result { let entrypoint = request.code[0].filename.clone(); let language_settings = self.find_language(&request.language).unwrap(); @@ -67,35 +106,6 @@ impl LambdoApiService { response } - - fn find_language( - &self, - language: &String, - ) -> Result> { - let language_list = &self.config.languages; - for lang in language_list { - if &*lang.name == language { - return Ok(lang.clone()); - } - } - Err("Language not found".into()) - } - - fn generate_steps( - language_settings: &LambdoLanguageConfig, - entrypoint: &str, - ) -> Vec { - let mut steps: Vec = Vec::new(); - for step in &language_settings.steps { - let command = step.command.replace("{{filename}}", entrypoint); - - steps.push(ExecuteRequestStep { - command, - enable_output: step.output.enabled, - }); - } - steps - } } #[cfg(test)] @@ -107,6 +117,7 @@ mod test { use super::LambdoApiService; use crate::{ + api::service::LambdoApiServiceTrait, config::{ LambdoAgentConfig, LambdoApiConfig, LambdoConfig, LambdoLanguageConfig, LambdoLanguageStepConfig, LambdoLanguageStepOutputConfig, LambdoVMMConfig, diff --git a/api/src/main.rs b/api/src/main.rs index d01a9aa..caf0c9e 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -9,7 +9,7 @@ use config::LambdoConfig; use thiserror::Error; use crate::{ - api::{run, service::LambdoApiService}, + api::{post_run_route, service::LambdoApiService}, vm_manager::grpc_definitions::lambdo_api_service_server::LambdoApiServiceServer, vm_manager::state::LambdoState, vm_manager::VMListener, @@ -87,8 +87,12 @@ async fn main() -> std::io::Result<()> { let http_port = config.api.web_port; let app_state = web::Data::new(api_service); info!("Starting web server on {}:{}", http_host, http_port); - HttpServer::new(move || App::new().app_data(app_state.clone()).service(run)) - .bind((http_host.clone(), http_port))? - .run() - .await + HttpServer::new(move || { + App::new() + .app_data(app_state.clone()) + .service(post_run_route) + }) + .bind((http_host.clone(), http_port))? + .run() + .await }