use std::{ env::args, io::{stdin, stdout, IsTerminal}, sync::Mutex, }; use actix_web::{ get, http::header::ContentType, post, web, App, HttpResponse, HttpServer, Responder, }; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] struct Registration { url: String, } trait AppState { fn register(&mut self, registration: &Registration) -> RegistrationResult; } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] enum RegistrationResult { RegistrationSuccess { id: String, url: String }, UrlAlreadyRegistered { url: String }, } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] enum RegistrationFailure { UrlAlreadyRegistered { url: String }, } #[post("/register")] async fn register( app_state: web::Data>, registration: web::Json, ) -> impl Responder { let result = app_state.lock().unwrap().register(®istration); match result { RegistrationResult::RegistrationSuccess { .. } => HttpResponse::Ok().json(result), RegistrationResult::UrlAlreadyRegistered { .. } => HttpResponse::BadRequest().json(result), } } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| App::new().service(register)) .bind(("127.0.0.1", 8080))? .run() .await } #[cfg(test)] mod app_tests { use std::sync::Arc; use actix_web::{body, http::header::ContentType, middleware::Logger, test, App}; use super::*; struct DummyAppState { id: String, } impl DummyAppState { fn new(id: String) -> Self { Self { id } } } impl AppState for DummyAppState { fn register(&mut self, registration: &Registration) -> RegistrationResult { if self.id == "" { return RegistrationResult::UrlAlreadyRegistered { url: registration.url.clone(), }; } else { RegistrationResult::RegistrationSuccess { id: self.id.clone(), url: registration.url.clone(), } } } } #[actix_web::test] async fn post_registration_returns_success_with_unique_id() { let id = "0123456789abcdef0123456789abcdef".to_string(); let dummy_state: Arc> = Arc::new(Mutex::new(DummyAppState::new(id.clone()))); // FIXME should only be called once, move to setup // env_logger::init(); let app = test::init_service( App::new() .wrap(Logger::default()) .app_data(web::Data::from(dummy_state)) .service(register), ) .await; let url = "http://192.168.1.1".to_string(); let req = test::TestRequest::post() .uri("/register") .set_json(Registration { url: url.clone() }) .insert_header(ContentType::json()) .to_request(); let resp = test::call_service(&app, req).await; assert!(resp.status().is_success()); let body = resp.into_body(); let bytes = body::to_bytes(body).await; assert_eq!( RegistrationResult::RegistrationSuccess { id, url }, serde_json::from_slice(&bytes.unwrap()).unwrap() ); } #[actix_web::test] async fn post_registration_fails_given_url_is_already_registered() { let dummy_state: Arc> = Arc::new(Mutex::new(DummyAppState::new("".to_string()))); let app = test::init_service( App::new() .wrap(Logger::default()) .app_data(web::Data::from(dummy_state)) .service(register), ) .await; let url = "http://192.168.1.1".to_string(); let _ = test::TestRequest::post() .uri("/register") .set_json(Registration { url: url.clone() }) .insert_header(ContentType::json()) .to_request(); // second request with the same URL let req = test::TestRequest::post() .uri("/register") .set_json(Registration { url: url.clone() }) .insert_header(ContentType::json()) .to_request(); let resp = test::call_service(&app, req).await; assert!(resp.status().is_client_error()); let body = resp.into_body(); let bytes = body::to_bytes(body).await; assert_eq!( RegistrationResult::UrlAlreadyRegistered { url }, serde_json::from_slice(&bytes.unwrap()).unwrap() ); } #[actix_web::test] async fn post_registration_fails_given_url_is_already_registered() { let id = "0123456789abcdef0123456789abcdef".to_string(); let id_generator: Arc = Arc::new(ConstantIdGenerator::new(id.clone())); let app = test::init_service( App::new() .wrap(Logger::default()) .app_data(web::Data::from(id_generator)) .service(register), ) .await; let url = "http://192.168.1.1".to_string(); let _ = test::TestRequest::post() .uri("/register") .set_json(Registration { url: url.clone() }) .insert_header(ContentType::json()) .to_request(); // second request with the same URL let req = test::TestRequest::post() .uri("/register") .set_json(Registration { url: url.clone() }) .insert_header(ContentType::json()) .to_request(); let resp = test::call_service(&app, req).await; assert!(resp.status().is_client_error()); let body = resp.into_body(); let bytes = body::to_bytes(body).await; assert_eq!( RegistrationResult::Failure(RegistrationFailure::UrlAlreadyRegistered { url }), serde_json::from_slice(&bytes.unwrap()).unwrap() ); } }