293 lines
10 KiB
Rust
293 lines
10 KiB
Rust
#[macro_use]
|
|
extern crate log;
|
|
|
|
use std::sync::RwLock;
|
|
|
|
use actix_cors::Cors;
|
|
use actix_web::middleware::TrailingSlash;
|
|
use actix_web::web::{Data, ServiceConfig};
|
|
use actix_web::{middleware, web, App, HttpServer};
|
|
|
|
use bank::bank::Bank;
|
|
|
|
use crate::handlers::close_account;
|
|
use crate::handlers::create_account;
|
|
use crate::handlers::deposit;
|
|
use crate::handlers::get_account;
|
|
use crate::handlers::get_account_nrs;
|
|
use crate::handlers::is_active;
|
|
use crate::handlers::pong;
|
|
use crate::handlers::transfer;
|
|
use crate::handlers::update_account;
|
|
use crate::handlers::withdraw;
|
|
|
|
pub struct AppState {
|
|
bank: RwLock<Bank>,
|
|
}
|
|
|
|
mod handlers;
|
|
|
|
fn config_app(app_data: Data<AppState>) -> Box<dyn Fn(&mut ServiceConfig)> {
|
|
Box::new(move |cfg: &mut ServiceConfig| {
|
|
cfg.app_data(app_data.clone()).service(pong::route).service(
|
|
web::scope("/accounts")
|
|
.service(create_account::route)
|
|
.service(get_account::route)
|
|
.service(deposit::route)
|
|
.service(close_account::route)
|
|
.service(get_account_nrs::route)
|
|
.service(withdraw::route)
|
|
.service(transfer::route)
|
|
.service(is_active::route)
|
|
.service(update_account::route),
|
|
);
|
|
})
|
|
}
|
|
|
|
#[actix_web::main]
|
|
async fn main() -> std::io::Result<()> {
|
|
pretty_env_logger::init();
|
|
|
|
let app_data = web::Data::new(AppState {
|
|
bank: RwLock::new(Bank::new()),
|
|
});
|
|
|
|
HttpServer::new(move || {
|
|
let cors = Cors::permissive();
|
|
|
|
App::new()
|
|
.wrap(cors)
|
|
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
|
|
.configure(config_app(app_data.clone()))
|
|
})
|
|
.bind(("127.0.0.1", 8000))?
|
|
.run()
|
|
.await
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use actix_web::body::to_bytes;
|
|
use actix_web::http::{Method as HttpMethod, StatusCode};
|
|
use actix_web::{test, App};
|
|
use serde_json::Value;
|
|
|
|
use crate::create_account::OwnerData;
|
|
use crate::handlers::AmountData;
|
|
use crate::transfer::TransferData;
|
|
|
|
use super::*;
|
|
|
|
#[actix_web::test]
|
|
async fn test_routes() {
|
|
// TODO: split up into separate tests
|
|
|
|
let app_data = web::Data::new(AppState {
|
|
bank: RwLock::new(Bank::new()),
|
|
});
|
|
|
|
let app = test::init_service(
|
|
App::new()
|
|
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
|
|
.configure(config_app(app_data)),
|
|
)
|
|
.await;
|
|
|
|
let payload = OwnerData {
|
|
owner: "aaa".to_string(),
|
|
};
|
|
let req = test::TestRequest::post()
|
|
.uri("/accounts")
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert_eq!(resp.status(), StatusCode::CREATED);
|
|
|
|
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
|
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
|
let nr = response_json["nr"].as_str().unwrap();
|
|
|
|
assert!(!nr.is_empty());
|
|
|
|
let req = test::TestRequest::default()
|
|
.method(HttpMethod::HEAD)
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let req = test::TestRequest::default()
|
|
.method(HttpMethod::HEAD)
|
|
.uri("/accounts/muh")
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
|
|
let payload = AmountData { amount: 5_f64 };
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/accounts/{}/withdraw", nr))
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(!resp.status().is_success());
|
|
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/accounts/{}/deposit", nr))
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let req = test::TestRequest::get()
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
|
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
|
let balance = response_json["balance"].as_f64().unwrap();
|
|
assert_eq!(5_f64, balance);
|
|
|
|
let payload = AmountData { amount: 10_f64 };
|
|
let req = test::TestRequest::put()
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert_eq!(resp.status(), StatusCode::PRECONDITION_REQUIRED);
|
|
|
|
let payload = AmountData { amount: 10_f64 };
|
|
let req = test::TestRequest::put()
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.append_header((actix_web::http::header::IF_MATCH, "I aspire to be an etag"))
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
|
|
|
|
let acc = bank::account::Account {
|
|
number: nr.to_string(),
|
|
owner: "aaa".to_string(),
|
|
balance: 5_f64,
|
|
is_active: true,
|
|
};
|
|
let payload = AmountData { amount: 10_f64 };
|
|
let req = test::TestRequest::put()
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.append_header((actix_web::http::header::IF_MATCH, acc.hash_string()))
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let req = test::TestRequest::put()
|
|
.uri("/accounts/muh")
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_client_error());
|
|
|
|
let payload = AmountData { amount: -10_f64 };
|
|
let req = test::TestRequest::put()
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_client_error());
|
|
|
|
let req = test::TestRequest::get()
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
|
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
|
let balance = response_json["balance"].as_f64().unwrap();
|
|
assert_eq!(10_f64, balance);
|
|
|
|
let payload = OwnerData {
|
|
owner: "bbb".to_string(),
|
|
};
|
|
let req = test::TestRequest::post()
|
|
.uri("/accounts")
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert_eq!(resp.status(), StatusCode::CREATED);
|
|
|
|
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
|
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
|
let nr2 = response_json["nr"].as_str().unwrap();
|
|
|
|
let payload = TransferData {
|
|
from: nr.to_string(),
|
|
to: nr2.to_string(),
|
|
amount: 5_f64,
|
|
};
|
|
let req = test::TestRequest::patch()
|
|
.uri("/accounts/transfer")
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let req = test::TestRequest::get()
|
|
.uri(&format!("/accounts/{}", nr2))
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
|
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
|
let balance = response_json["balance"].as_f64().unwrap();
|
|
assert_eq!(5_f64, balance);
|
|
|
|
let req = test::TestRequest::delete()
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(!resp.status().is_success());
|
|
|
|
let payload = AmountData { amount: 5_f64 };
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/accounts/{}/withdraw", nr))
|
|
.set_form(&payload)
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let req = test::TestRequest::get().uri("/accounts").to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
|
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
|
let nrs = response_json.as_array().unwrap();
|
|
assert_eq!(2, nrs.len());
|
|
|
|
let req = test::TestRequest::delete()
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let req = test::TestRequest::default()
|
|
.method(HttpMethod::HEAD)
|
|
.uri(&format!("/accounts/{}", nr))
|
|
.to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert_eq!(resp.status(), StatusCode::GONE);
|
|
|
|
let req = test::TestRequest::get().uri("/accounts").to_request();
|
|
let resp = test::call_service(&app, req).await;
|
|
assert!(resp.status().is_success());
|
|
|
|
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
|
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
|
let nrs = response_json.as_array().unwrap();
|
|
assert_eq!(1, nrs.len());
|
|
}
|
|
}
|