146 lines
5.1 KiB
Rust
146 lines
5.1 KiB
Rust
|
|
use axum::Router;
|
||
|
|
use axum::extract::{Path, State};
|
||
|
|
use axum::http::StatusCode;
|
||
|
|
use axum::routing::{delete, get, put};
|
||
|
|
use axum_msgpack::MsgPack;
|
||
|
|
|
||
|
|
use super::models::{Place, PlaceUpsert};
|
||
|
|
use super::repository::{PlacesError, PlacesRepository};
|
||
|
|
|
||
|
|
type Result<T, E = (StatusCode, String)> = std::result::Result<T, E>;
|
||
|
|
|
||
|
|
fn internal_error(err: PlacesError) -> (StatusCode, String) {
|
||
|
|
match err {
|
||
|
|
PlacesError::FailToGet(_) | PlacesError::FailToUpsert(_) | PlacesError::FailToDelete(_) => {
|
||
|
|
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
|
||
|
|
}
|
||
|
|
PlacesError::NotFound(_) => (StatusCode::NOT_FOUND, err.to_string()),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn get_places<PR: PlacesRepository>(
|
||
|
|
State(repository): State<PR>,
|
||
|
|
) -> Result<MsgPack<Vec<Place>>> {
|
||
|
|
let places = repository.get_places().await.map_err(internal_error)?;
|
||
|
|
Ok(MsgPack(places))
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn upsert_place<PR: PlacesRepository>(
|
||
|
|
State(repository): State<PR>,
|
||
|
|
MsgPack(place): MsgPack<PlaceUpsert>,
|
||
|
|
) -> Result<MsgPack<Place>> {
|
||
|
|
let place = match place.into() {
|
||
|
|
(place, Some(id)) => repository.update_place((place, id).into()).await,
|
||
|
|
(place, None) => repository.insert_place(place).await,
|
||
|
|
}
|
||
|
|
.map_err(internal_error)?;
|
||
|
|
Ok(MsgPack(place))
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn delete_place<PR: PlacesRepository>(
|
||
|
|
State(repository): State<PR>,
|
||
|
|
Path(id): Path<i64>,
|
||
|
|
) -> Result<()> {
|
||
|
|
repository.delete_place(id).await.map_err(internal_error)?;
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn places_routes<PR: PlacesRepository>(repository: PR) -> Router {
|
||
|
|
Router::new()
|
||
|
|
.route("/", get(get_places::<PR>))
|
||
|
|
.route("/", put(upsert_place::<PR>))
|
||
|
|
.route("/{id}", delete(delete_place::<PR>))
|
||
|
|
.with_state(repository)
|
||
|
|
}
|
||
|
|
|
||
|
|
mod tests {
|
||
|
|
#![cfg(test)]
|
||
|
|
use super::places_routes;
|
||
|
|
use crate::places::models::{Place, PlaceUpsert};
|
||
|
|
use crate::places::repository::MockPlacesRepository;
|
||
|
|
|
||
|
|
use anyhow::Result;
|
||
|
|
use axum::Router;
|
||
|
|
use axum::http::StatusCode;
|
||
|
|
use axum_test::TestServer;
|
||
|
|
|
||
|
|
fn setup_server() -> Result<(TestServer, MockPlacesRepository)> {
|
||
|
|
let places_repository = MockPlacesRepository::new();
|
||
|
|
let router = Router::new().nest("/places", places_routes(places_repository.clone()));
|
||
|
|
Ok((TestServer::new(router)?, places_repository))
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_add_place() -> Result<()> {
|
||
|
|
let (server, mock_repository) = setup_server()?;
|
||
|
|
let place = PlaceUpsert {
|
||
|
|
id: None,
|
||
|
|
name: "Sherlock Holmes".to_owned(),
|
||
|
|
address: "221 B Baker Street, London".to_owned(),
|
||
|
|
description: "Museum and Gift Shop".to_owned(),
|
||
|
|
icon: "museum".to_owned(),
|
||
|
|
latitude: 51.5237669,
|
||
|
|
longitude: -0.1627829,
|
||
|
|
open_hours: "Tu-Su 09:30-18:00".to_owned(),
|
||
|
|
url: Some("https://www.sherlock-holmes.co.uk/".to_owned()),
|
||
|
|
};
|
||
|
|
// Insert the place
|
||
|
|
let res = server.put("/places").msgpack(&place).await;
|
||
|
|
// We should get a success on the request
|
||
|
|
assert_eq!(res.status_code(), StatusCode::OK);
|
||
|
|
let _res_place: Place = res.msgpack();
|
||
|
|
// The correct function should be called
|
||
|
|
assert_eq!(mock_repository.insert_place_count().await, 1);
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_get_places() -> Result<()> {
|
||
|
|
let (server, mock_repository) = setup_server()?;
|
||
|
|
// Get the places
|
||
|
|
let res = server.get("/places").await;
|
||
|
|
// We should get a success on the request
|
||
|
|
assert_eq!(res.status_code(), StatusCode::OK);
|
||
|
|
let _res_places: Vec<Place> = res.msgpack();
|
||
|
|
// and the correct function should be called
|
||
|
|
assert_eq!(mock_repository.get_places_count().await, 1);
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_delete() -> Result<()> {
|
||
|
|
let (server, mock_repository) = setup_server()?;
|
||
|
|
// Call delete
|
||
|
|
let res = server.delete("/places/0").await;
|
||
|
|
// We should get a success on the request
|
||
|
|
assert_eq!(res.status_code(), StatusCode::OK);
|
||
|
|
// The correct function should be called
|
||
|
|
assert_eq!(mock_repository.delete_place_count().await, 1);
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_update() -> Result<()> {
|
||
|
|
let (server, mock_repository) = setup_server()?;
|
||
|
|
let places = PlaceUpsert {
|
||
|
|
id: Some(1),
|
||
|
|
name: "Sherlock Holmes".to_owned(),
|
||
|
|
address: "221 B Baker Street, London".to_owned(),
|
||
|
|
description: "Museum and Gift Shop".to_owned(),
|
||
|
|
icon: "museum".to_owned(),
|
||
|
|
latitude: 51.5237669,
|
||
|
|
longitude: -0.1627829,
|
||
|
|
open_hours: "Tu-Su 09:30-18:00".to_owned(),
|
||
|
|
url: Some("https://www.sherlock-holmes.co.uk/".to_owned()),
|
||
|
|
};
|
||
|
|
// upsert the place
|
||
|
|
let res = server.put("/places").msgpack(&places).await;
|
||
|
|
// We should get a success on the request
|
||
|
|
assert_eq!(res.status_code(), StatusCode::OK);
|
||
|
|
let _res_place: Place = res.msgpack();
|
||
|
|
// The correct function should be called
|
||
|
|
assert_eq!(mock_repository.update_place_count().await, 1);
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|