Skip to content

Commit

Permalink
Add create one endpoint, add data in test via endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
evanjt committed Oct 1, 2024
1 parent 8214e15 commit 9ff75ef
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 82 deletions.
19 changes: 16 additions & 3 deletions src/common/macros/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ macro_rules! generate_router {
resource_name: $resource_name:expr,
db_entity: $db_entity:ty,
db_model: $db_model:ty,
active_model: $active_model:ty,
db_columns: $db_columns:ty,
get_one_response_model: $get_one_response_model:ty,
get_all_response_model: $get_all_response_model:ty,
create_one_request_model: $create_one_request_model:ty,
order_column_logic: $order_column_logic:expr,
searchable_columns: $searchable_columns:expr // New argument for searchable fields
searchable_columns: $searchable_columns:expr
) => {
use crate::common::models::FilterOptions;
use crate::common::sort::generic_sort;
Expand All @@ -23,13 +25,13 @@ macro_rules! generate_router {
};
use sea_orm::query::*;
use sea_orm::ColumnTrait;
use sea_orm::{DatabaseConnection, EntityTrait};
use sea_orm::{DatabaseConnection, EntityTrait,ActiveModelTrait};
use std::iter::Iterator;
use uuid::Uuid;

pub fn router(db: DatabaseConnection) -> Router {
Router::new()
.route("/", routing::get(get_all))
.route("/", routing::get(get_all).post(create_one))
.route("/:id", routing::get(get_one))
.with_state(db)
}
Expand Down Expand Up @@ -102,5 +104,16 @@ macro_rules! generate_router {

}

#[utoipa::path(post, path = format!("/v1/{}", $resource_name), responses((status = 201, body = $get_one_response_model)))]
pub async fn create_one(
State(db): State<DatabaseConnection>,
Json(payload): Json<$create_one_request_model>,
) -> impl IntoResponse {
let db_obj: $active_model = <$active_model>::from(payload);
let response = db_obj.insert(&db).await.unwrap();
let response_obj: $get_one_response_model = response.into();

(StatusCode::CREATED, Json(response_obj))
}
};
}
53 changes: 50 additions & 3 deletions src/soil/profiles/models.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use chrono::NaiveDateTime;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(ToSchema, Serialize)]
#[derive(ToSchema, Serialize, Deserialize)]
pub struct SoilProfile {
pub id: Uuid,
pub name: String,
Expand All @@ -25,7 +25,7 @@ pub struct SoilProfile {
pub parent_material: Option<f64>,
}

#[derive(ToSchema, Serialize)]
#[derive(ToSchema, Serialize, Deserialize)]
pub struct SoilProfileBasic {
pub id: Uuid,
pub last_updated: chrono::NaiveDateTime,
Expand Down Expand Up @@ -67,3 +67,50 @@ impl From<crate::soil::profiles::db::Model> for SoilProfile {
}
}
}

#[derive(ToSchema, Serialize, Deserialize)]
pub struct SoilProfileCreate {
pub name: String,
pub profile_iterator: i32,
pub gradient: String,
pub description_horizon: Option<Value>,
pub weather: Option<String>,
pub topography: Option<String>,
pub vegetation_type: Option<String>,
pub aspect: Option<String>,
pub lythology_surficial_deposit: Option<String>,
pub soil_type_id: Uuid,
pub area_id: Uuid,
pub soil_diagram: Option<String>,
pub photo: Option<String>,
pub parent_material: Option<f64>,
}

impl From<SoilProfileCreate> for crate::soil::profiles::db::ActiveModel {
fn from(soil_profile: SoilProfileCreate) -> Self {
let now = chrono::Utc::now().naive_utc();

crate::soil::profiles::db::ActiveModel {
id: sea_orm::ActiveValue::Set(Uuid::new_v4()),
last_updated: sea_orm::ActiveValue::Set(chrono::Utc::now().naive_utc()),
name: sea_orm::ActiveValue::Set(soil_profile.name),
profile_iterator: sea_orm::ActiveValue::Set(soil_profile.profile_iterator),
gradient: sea_orm::ActiveValue::Set(soil_profile.gradient),
description_horizon: sea_orm::ActiveValue::Set(soil_profile.description_horizon),
weather: sea_orm::ActiveValue::Set(soil_profile.weather),
topography: sea_orm::ActiveValue::Set(soil_profile.topography),
vegetation_type: sea_orm::ActiveValue::Set(soil_profile.vegetation_type),
aspect: sea_orm::ActiveValue::Set(soil_profile.aspect),
lythology_surficial_deposit: sea_orm::ActiveValue::Set(
soil_profile.lythology_surficial_deposit,
),
created_on: sea_orm::ActiveValue::Set(Some(now)),
soil_type_id: sea_orm::ActiveValue::Set(soil_profile.soil_type_id),
area_id: sea_orm::ActiveValue::Set(soil_profile.area_id),
soil_diagram: sea_orm::ActiveValue::Set(soil_profile.soil_diagram),
photo: sea_orm::ActiveValue::Set(soil_profile.photo),
parent_material: sea_orm::ActiveValue::Set(soil_profile.parent_material),
iterator: sea_orm::ActiveValue::NotSet,
}
}
}
2 changes: 2 additions & 0 deletions src/soil/profiles/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ generate_router!(
resource_name: "soil_profiles",
db_entity: crate::soil::profiles::db::Entity,
db_model: crate::soil::profiles::db::Model,
active_model: crate::soil::profiles::db::ActiveModel,
db_columns: crate::soil::profiles::db::Column,
get_one_response_model: crate::soil::profiles::models::SoilProfile,
get_all_response_model: crate::soil::profiles::models::SoilProfileBasic,
create_one_request_model: crate::soil::profiles::models::SoilProfileCreate,
order_column_logic: [
("id", crate::soil::profiles::db::Column::Id),
("name", crate::soil::profiles::db::Column::Name),
Expand Down
33 changes: 30 additions & 3 deletions src/soil/types/models.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use serde::Serialize;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(ToSchema, Serialize)]
#[derive(ToSchema, Serialize, Deserialize)]
pub struct SoilType {
pub id: Uuid,
pub last_updated: chrono::NaiveDateTime,
Expand All @@ -11,7 +11,7 @@ pub struct SoilType {
pub image: Option<String>,
}

#[derive(ToSchema, Serialize)]
#[derive(ToSchema, Serialize, Deserialize)]
pub struct SoilTypeBasic {
pub id: Uuid,
pub last_updated: chrono::NaiveDateTime,
Expand Down Expand Up @@ -41,3 +41,30 @@ impl From<crate::soil::types::db::Model> for SoilType {
}
}
}

#[derive(ToSchema, Serialize, Deserialize)]
pub struct SoilTypeCreate {
pub name: String,
pub description: String,
pub image: Option<String>,
}

impl From<SoilTypeCreate> for crate::soil::types::db::ActiveModel {
fn from(soil_type: SoilTypeCreate) -> Self {
crate::soil::types::db::ActiveModel {
id: sea_orm::ActiveValue::Set(Uuid::new_v4()),
last_updated: sea_orm::ActiveValue::Set(chrono::Utc::now().naive_utc()),
name: sea_orm::ActiveValue::Set(soil_type.name),
description: sea_orm::ActiveValue::Set(soil_type.description),
image: sea_orm::ActiveValue::Set(soil_type.image),
iterator: sea_orm::ActiveValue::NotSet,
}
}
}

#[derive(ToSchema, Serialize, Deserialize)]
pub struct SoilTypeUpdate {
pub name: Option<String>,
pub description: Option<String>,
pub image: Option<String>,
}
2 changes: 2 additions & 0 deletions src/soil/types/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ generate_router!(
resource_name: "soil_types",
db_entity: crate::soil::types::db::Entity,
db_model: crate::soil::types::db::Model,
active_model: crate::soil::types::db::ActiveModel,
db_columns: crate::soil::types::db::Column,
get_one_response_model: crate::soil::types::models::SoilType,
get_all_response_model: crate::soil::types::models::SoilTypeBasic,
create_one_request_model: crate::soil::types::models::SoilTypeCreate,
order_column_logic: [
("id", crate::soil::types::db::Column::Id),
("name", crate::soil::types::db::Column::Name),
Expand Down
28 changes: 1 addition & 27 deletions tests/mock_fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ use axum::routing::Router;
use rstest::fixture;
use sea_orm::sea_query::TableCreateStatement;
use sea_orm::Database;
use sea_orm::{ActiveModelTrait, ConnectionTrait, DatabaseConnection, Schema};
use sea_orm::{ConnectionTrait, DatabaseConnection, Schema};
use soil_api_rust::soil::types::db::Entity;
use soil_api_rust::soil::types::views::router;
use uuid::Uuid;

pub async fn setup_database() -> DatabaseConnection {
// Use an in-memory SQLite database for testing.
Expand All @@ -21,34 +20,9 @@ pub async fn setup_database() -> DatabaseConnection {
db
}

pub async fn insert_mock_data(db: &DatabaseConnection) {
let now = chrono::Utc::now().naive_utc();

let soil_type_1 = soil_api_rust::soil::types::db::ActiveModel {
id: sea_orm::ActiveValue::Set(Uuid::new_v4()),
iterator: sea_orm::ActiveValue::Set(1),
last_updated: sea_orm::ActiveValue::Set(now),
name: sea_orm::ActiveValue::Set("Clay".to_string()),
description: sea_orm::ActiveValue::Set("Clay soil type".to_string()),
image: sea_orm::ActiveValue::Set(Some("clay.png".to_string())),
};
soil_type_1.insert(db).await.unwrap();

let soil_type_2 = soil_api_rust::soil::types::db::ActiveModel {
id: sea_orm::ActiveValue::Set(Uuid::new_v4()),
iterator: sea_orm::ActiveValue::Set(2),
last_updated: sea_orm::ActiveValue::Set(now),
name: sea_orm::ActiveValue::Set("Sand".to_string()),
description: sea_orm::ActiveValue::Set("Sandy soil type".to_string()),
image: sea_orm::ActiveValue::Set(None),
};
soil_type_2.insert(db).await.unwrap();
}

#[fixture]
pub async fn mock_api() -> Router {
// Use a unique in-memory SQLite database per test run
let db = setup_database().await;
insert_mock_data(&db).await;
router(db)
}
86 changes: 40 additions & 46 deletions tests/soil_type_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,36 @@ use tower::ServiceExt;
mod mock_fixtures;
use mock_fixtures::mock_api;
use serde_json::{from_slice, Value};
use uuid::Uuid;

#[rstest]
#[tokio::test]
async fn test_get_all_soil_types(#[future(awt)] mock_api: Router) {
let now = chrono::Utc::now().naive_utc();

let soil_type_1 = serde_json::json!({
"id": Uuid::new_v4(),
"iterator": 1,
"last_updated": now,
let soil_type = serde_json::json!({
"name": "Clay",
"description": "Clay soil type",
"image": "clay.png"
});

let soil_type_2 = serde_json::json!({
"id": Uuid::new_v4(),
"iterator": 2,
"last_updated": now,
"name": "Sand",
"description": "Sandy soil type",
"image": null
});
// Insert mock data into the API with the POST method.

// Add objects to the POST endpoint
let response = mock_api
.clone()
.oneshot(
Request::builder()
.method("POST")
.uri("/")
.header("Content-Type", "application/json")
.body(Body::from(soil_type.clone().to_string()))
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::CREATED);

// Get all from API
let response = mock_api
.clone()
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
Expand All @@ -45,38 +48,29 @@ async fn test_get_all_soil_types(#[future(awt)] mock_api: Router) {

// Assert that the body is an array and contains expected mock data.
assert!(body_json.is_array(), "Response body is not an array");
let soil_types = body_json.as_array().unwrap();

// Assert that at least two soil types are present in the response.
assert!(soil_types.len() >= 2, "Expected at least 2 soil types");

// Check each soil type in the array, by looping through them and checking their fields.
for soil_type in soil_types {
assert!(soil_type.is_object());
let soil_type = soil_type.as_object().unwrap();
let soil_type = body_json.as_array().unwrap();
println!("{:?}", soil_type);

// Check that all necessary fields exist in the soil type.
assert!(soil_type.get("id").is_some());
// assert!(soil_type.get("iterator").is_some());
assert!(soil_type.get("last_updated").is_some());
assert!(soil_type.get("name").is_some());
assert!(soil_type.get("description").is_some());
// assert!(soil_type.get("image").is_some());
}
// // Assert that at least two soil types are present in the response.
assert!(
soil_type.len() == 1,
"Expected only 1 soil type, found {}",
soil_type.len()
);

let clay_soil = &soil_types[0];
let sand_soil = &soil_types[1];
assert!(soil_type[0].is_object());

assert_eq!(clay_soil["name"], "Clay");
assert_eq!(clay_soil["description"], "Clay soil type");
assert_eq!(sand_soil["name"], "Sand");
assert_eq!(sand_soil["description"], "Sandy soil type");
// Check the soil type matches the expected mock data from above.
let soil_type = soil_type[0].as_object().unwrap();
assert_eq!(
soil_type.get("name").unwrap(),
&serde_json::Value::String("Clay".to_string())
);
assert_eq!(
soil_type.get("description").unwrap(),
&serde_json::Value::String("Clay soil type".to_string())
);

// Check that all necessary fields exist in the first soil type.
assert!(clay_soil.get("id").is_some());
// assert!(clay_soil.get("iterator").is_some());
assert!(clay_soil.get("last_updated").is_some());
assert!(clay_soil.get("name").is_some());
assert!(clay_soil.get("description").is_some());
// assert!(clay_soil.get("image").is_some());
// Should not be in response on getall);
assert_eq!(soil_type.get("image"), None);
}

0 comments on commit 9ff75ef

Please sign in to comment.