Add id check for booking

It now checks if sales_person_id and slot_id actually exists.
This commit is contained in:
Simon Goller 2024-05-09 08:11:38 +02:00
parent 418a2944f7
commit bc8a534353
10 changed files with 201 additions and 24 deletions

View file

@ -23,6 +23,8 @@ type BookingService = service_impl::booking::BookingServiceImpl<
PermissionService, PermissionService,
ClockService, ClockService,
UuidService, UuidService,
SalesPersonService,
SlotService,
>; >;
#[derive(Clone)] #[derive(Clone)]
@ -31,7 +33,6 @@ pub struct RestStateImpl {
slot_service: Arc<SlotService>, slot_service: Arc<SlotService>,
sales_person_service: Arc<SalesPersonService>, sales_person_service: Arc<SalesPersonService>,
booking_service: Arc<BookingService>, booking_service: Arc<BookingService>,
} }
impl rest::RestStateDef for RestStateImpl { impl rest::RestStateDef for RestStateImpl {
type PermissionService = PermissionService; type PermissionService = PermissionService;
@ -90,6 +91,8 @@ impl RestStateImpl {
permission_service.clone(), permission_service.clone(),
clock_service, clock_service,
uuid_service, uuid_service,
sales_person_service.clone(),
slot_service.clone(),
)); ));
Self { Self {
permission_service, permission_service,

View file

@ -10,7 +10,7 @@ use time::PrimitiveDateTime;
use uuid::Uuid; use uuid::Uuid;
use crate::{error_handler, RestStateDef}; use crate::{error_handler, RestStateDef};
use service::booking::{BookingService, Booking}; use service::booking::{Booking, BookingService};
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BookingTO { pub struct BookingTO {
@ -65,9 +65,7 @@ pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
.route("/:id", delete(delete_booking::<RestState>)) .route("/:id", delete(delete_booking::<RestState>))
} }
pub async fn get_all_bookings<RestState: RestStateDef>( pub async fn get_all_bookings<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
rest_state: State<RestState>,
) -> Response {
error_handler( error_handler(
(async { (async {
let bookings: Arc<[BookingTO]> = rest_state let bookings: Arc<[BookingTO]> = rest_state
@ -95,7 +93,9 @@ pub async fn get_booking<RestState: RestStateDef>(
let booking = rest_state.booking_service().get(booking_id, ()).await?; let booking = rest_state.booking_service().get(booking_id, ()).await?;
Ok(Response::builder() Ok(Response::builder()
.status(200) .status(200)
.body(Body::new(serde_json::to_string(&BookingTO::from(&booking)).unwrap())) .body(Body::new(
serde_json::to_string(&BookingTO::from(&booking)).unwrap(),
))
.unwrap()) .unwrap())
}) })
.await, .await,
@ -108,10 +108,15 @@ pub async fn create_booking<RestState: RestStateDef>(
) -> Response { ) -> Response {
error_handler( error_handler(
(async { (async {
let booking = rest_state.booking_service().create(&Booking::from(&booking), ()).await?; let booking = rest_state
.booking_service()
.create(&Booking::from(&booking), ())
.await?;
Ok(Response::builder() Ok(Response::builder()
.status(200) .status(200)
.body(Body::new(serde_json::to_string(&BookingTO::from(&booking)).unwrap())) .body(Body::new(
serde_json::to_string(&BookingTO::from(&booking)).unwrap(),
))
.unwrap()) .unwrap())
}) })
.await, .await,
@ -125,11 +130,8 @@ pub async fn delete_booking<RestState: RestStateDef>(
error_handler( error_handler(
(async { (async {
rest_state.booking_service().delete(booking_id, ()).await?; rest_state.booking_service().delete(booking_id, ()).await?;
Ok(Response::builder() Ok(Response::builder().status(200).body(Body::empty()).unwrap())
.status(200)
.body(Body::empty())
.unwrap())
}) })
.await, .await,
) )
} }

View file

@ -22,6 +22,8 @@ pub use permission::User;
pub enum ValidationFailureItem { pub enum ValidationFailureItem {
ModificationNotAllowed(Arc<str>), ModificationNotAllowed(Arc<str>),
InvalidValue(Arc<str>), InvalidValue(Arc<str>),
IdDoesNotExist(Arc<str>, Uuid),
Duplicate,
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]

View file

@ -44,6 +44,7 @@ pub trait SalesPersonService {
async fn get_all(&self, context: Self::Context) -> Result<Arc<[SalesPerson]>, ServiceError>; async fn get_all(&self, context: Self::Context) -> Result<Arc<[SalesPerson]>, ServiceError>;
async fn get(&self, id: Uuid, context: Self::Context) -> Result<SalesPerson, ServiceError>; async fn get(&self, id: Uuid, context: Self::Context) -> Result<SalesPerson, ServiceError>;
async fn exists(&self, id: Uuid, context: Self::Context) -> Result<bool, ServiceError>;
async fn create( async fn create(
&self, &self,
item: &SalesPerson, item: &SalesPerson,

View file

@ -89,6 +89,7 @@ pub trait SlotService {
async fn get_slots(&self, context: Self::Context) -> Result<Arc<[Slot]>, ServiceError>; async fn get_slots(&self, context: Self::Context) -> Result<Arc<[Slot]>, ServiceError>;
async fn get_slot(&self, id: &Uuid, context: Self::Context) -> Result<Slot, ServiceError>; async fn get_slot(&self, id: &Uuid, context: Self::Context) -> Result<Slot, ServiceError>;
async fn exists(&self, id: Uuid, context: Self::Context) -> Result<bool, ServiceError>;
async fn create_slot(&self, slot: &Slot, context: Self::Context) -> Result<Slot, ServiceError>; async fn create_slot(&self, slot: &Slot, context: Self::Context) -> Result<Slot, ServiceError>;
async fn delete_slot(&self, id: &Uuid, context: Self::Context) -> Result<(), ServiceError>; async fn delete_slot(&self, id: &Uuid, context: Self::Context) -> Result<(), ServiceError>;
async fn update_slot(&self, slot: &Slot, context: Self::Context) -> Result<(), ServiceError>; async fn update_slot(&self, slot: &Slot, context: Self::Context) -> Result<(), ServiceError>;

View file

@ -8,49 +8,84 @@ use uuid::Uuid;
const BOOKING_SERVICE_PROCESS: &str = "booking-service"; const BOOKING_SERVICE_PROCESS: &str = "booking-service";
pub struct BookingServiceImpl<BookingDao, PermissionService, ClockService, UuidService> pub struct BookingServiceImpl<
where BookingDao,
PermissionService,
ClockService,
UuidService,
SalesPersonService,
SlotService,
> where
BookingDao: dao::booking::BookingDao + Send + Sync, BookingDao: dao::booking::BookingDao + Send + Sync,
PermissionService: service::permission::PermissionService + Send + Sync, PermissionService: service::permission::PermissionService + Send + Sync,
UuidService: service::uuid_service::UuidService + Send + Sync, UuidService: service::uuid_service::UuidService + Send + Sync,
ClockService: service::clock::ClockService + Send + Sync, ClockService: service::clock::ClockService + Send + Sync,
SalesPersonService: service::sales_person::SalesPersonService + Send + Sync,
SlotService: service::slot::SlotService + Send + Sync,
{ {
pub booking_dao: Arc<BookingDao>, pub booking_dao: Arc<BookingDao>,
pub permission_service: Arc<PermissionService>, pub permission_service: Arc<PermissionService>,
pub clock_service: Arc<ClockService>, pub clock_service: Arc<ClockService>,
pub uuid_service: Arc<UuidService>, pub uuid_service: Arc<UuidService>,
pub sales_person_service: Arc<SalesPersonService>,
pub slot_service: Arc<SlotService>,
} }
impl<BookingDao, PermissionService, ClockService, UuidService> impl<BookingDao, PermissionService, ClockService, UuidService, SalesPersonService, SlotService>
BookingServiceImpl<BookingDao, PermissionService, ClockService, UuidService> BookingServiceImpl<
BookingDao,
PermissionService,
ClockService,
UuidService,
SalesPersonService,
SlotService,
>
where where
BookingDao: dao::booking::BookingDao + Send + Sync, BookingDao: dao::booking::BookingDao + Send + Sync,
PermissionService: service::permission::PermissionService + Send + Sync, PermissionService: service::permission::PermissionService + Send + Sync,
UuidService: service::uuid_service::UuidService + Send + Sync, UuidService: service::uuid_service::UuidService + Send + Sync,
ClockService: service::clock::ClockService + Send + Sync, ClockService: service::clock::ClockService + Send + Sync,
SalesPersonService: service::sales_person::SalesPersonService + Send + Sync,
SlotService: service::slot::SlotService + Send + Sync,
{ {
pub fn new( pub fn new(
booking_dao: Arc<BookingDao>, booking_dao: Arc<BookingDao>,
permission_service: Arc<PermissionService>, permission_service: Arc<PermissionService>,
clock_service: Arc<ClockService>, clock_service: Arc<ClockService>,
uuid_service: Arc<UuidService>, uuid_service: Arc<UuidService>,
sales_person_service: Arc<SalesPersonService>,
slot_service: Arc<SlotService>,
) -> Self { ) -> Self {
Self { Self {
booking_dao, booking_dao,
permission_service, permission_service,
clock_service, clock_service,
uuid_service, uuid_service,
sales_person_service,
slot_service,
} }
} }
} }
#[async_trait] #[async_trait]
impl<BookingDao, PermissionService, ClockService, UuidService> BookingService impl<BookingDao, PermissionService, ClockService, UuidService, SalesPersonService, SlotService>
for BookingServiceImpl<BookingDao, PermissionService, ClockService, UuidService> BookingService
for BookingServiceImpl<
BookingDao,
PermissionService,
ClockService,
UuidService,
SalesPersonService,
SlotService,
>
where where
BookingDao: dao::booking::BookingDao + Send + Sync, BookingDao: dao::booking::BookingDao + Send + Sync,
PermissionService: service::permission::PermissionService + Send + Sync, PermissionService: service::permission::PermissionService + Send + Sync,
UuidService: service::uuid_service::UuidService + Send + Sync, UuidService: service::uuid_service::UuidService + Send + Sync,
ClockService: service::clock::ClockService + Send + Sync, ClockService: service::clock::ClockService + Send + Sync,
SalesPersonService: service::sales_person::SalesPersonService<Context = PermissionService::Context>
+ Send
+ Sync,
SlotService: service::slot::SlotService<Context = PermissionService::Context> + Send + Sync,
{ {
type Context = PermissionService::Context; type Context = PermissionService::Context;
@ -85,7 +120,7 @@ where
context: Self::Context, context: Self::Context,
) -> Result<Booking, ServiceError> { ) -> Result<Booking, ServiceError> {
self.permission_service self.permission_service
.check_permission("hr", context) .check_permission("hr", context.clone())
.await?; .await?;
if booking.id != Uuid::nil() { if booking.id != Uuid::nil() {
@ -113,6 +148,28 @@ where
if booking.calendar_week > 53 { if booking.calendar_week > 53 {
validation.push(ValidationFailureItem::InvalidValue("calendar_week".into())); validation.push(ValidationFailureItem::InvalidValue("calendar_week".into()));
} }
if self
.sales_person_service
.exists(booking.sales_person_id, context.clone())
.await?
== false
{
validation.push(ValidationFailureItem::IdDoesNotExist(
"sales_person_id".into(),
booking.sales_person_id,
));
}
if self
.slot_service
.exists(booking.slot_id, context.clone())
.await?
== false
{
validation.push(ValidationFailureItem::IdDoesNotExist(
"slot_id".into(),
booking.slot_id,
));
}
if !validation.is_empty() { if !validation.is_empty() {
return Err(ServiceError::ValidationError(validation.into())); return Err(ServiceError::ValidationError(validation.into()));

View file

@ -86,6 +86,14 @@ where
.ok_or(ServiceError::EntityNotFound(id)) .ok_or(ServiceError::EntityNotFound(id))
} }
async fn exists(&self, id: Uuid, _context: Self::Context) -> Result<bool, ServiceError> {
Ok(self
.sales_person_dao
.find_by_id(id)
.await
.map(|x| x.is_some())?)
}
async fn create( async fn create(
&self, &self,
sales_person: &SalesPerson, sales_person: &SalesPerson,

View file

@ -91,6 +91,11 @@ where
.ok_or_else(move || ServiceError::EntityNotFound(*id))?; .ok_or_else(move || ServiceError::EntityNotFound(*id))?;
Ok(slot) Ok(slot)
} }
async fn exists(&self, id: Uuid, _context: Self::Context) -> Result<bool, ServiceError> {
Ok(self.slot_dao.get_slot(&id).await.map(|s| s.is_some())?)
}
async fn create_slot(&self, slot: &Slot, context: Self::Context) -> Result<Slot, ServiceError> { async fn create_slot(&self, slot: &Slot, context: Self::Context) -> Result<Slot, ServiceError> {
self.permission_service self.permission_service
.check_permission("hr", context.clone()) .check_permission("hr", context.clone())

View file

@ -2,8 +2,9 @@ use crate::test::error_test::*;
use dao::booking::{BookingEntity, MockBookingDao}; use dao::booking::{BookingEntity, MockBookingDao};
use mockall::predicate::eq; use mockall::predicate::eq;
use service::{ use service::{
booking::Booking, clock::MockClockService, uuid_service::MockUuidService, booking::Booking, clock::MockClockService, sales_person::MockSalesPersonService,
MockPermissionService, ValidationFailureItem, slot::MockSlotService, uuid_service::MockUuidService, MockPermissionService,
ValidationFailureItem,
}; };
use time::{Date, Month, PrimitiveDateTime, Time}; use time::{Date, Month, PrimitiveDateTime, Time};
use uuid::{uuid, Uuid}; use uuid::{uuid, Uuid};
@ -67,17 +68,27 @@ pub struct BookingServiceDependencies {
pub permission_service: MockPermissionService, pub permission_service: MockPermissionService,
pub clock_service: MockClockService, pub clock_service: MockClockService,
pub uuid_service: MockUuidService, pub uuid_service: MockUuidService,
pub sales_person_service: MockSalesPersonService,
pub slot_service: MockSlotService,
} }
impl BookingServiceDependencies { impl BookingServiceDependencies {
pub fn build_service( pub fn build_service(
self, self,
) -> BookingServiceImpl<MockBookingDao, MockPermissionService, MockClockService, MockUuidService> ) -> BookingServiceImpl<
{ MockBookingDao,
MockPermissionService,
MockClockService,
MockUuidService,
MockSalesPersonService,
MockSlotService,
> {
BookingServiceImpl::new( BookingServiceImpl::new(
self.booking_dao.into(), self.booking_dao.into(),
self.permission_service.into(), self.permission_service.into(),
self.clock_service.into(), self.clock_service.into(),
self.uuid_service.into(), self.uuid_service.into(),
self.sales_person_service.into(),
self.slot_service.into(),
) )
} }
} }
@ -113,11 +124,20 @@ pub fn build_dependencies(permission: bool, role: &'static str) -> BookingServic
}); });
let uuid_service = MockUuidService::new(); let uuid_service = MockUuidService::new();
let mut sales_person_service = MockSalesPersonService::new();
sales_person_service
.expect_exists()
.returning(|_, _| Ok(true));
let mut slot_service = MockSlotService::new();
slot_service.expect_exists().returning(|_, _| Ok(true));
BookingServiceDependencies { BookingServiceDependencies {
booking_dao, booking_dao,
permission_service, permission_service,
clock_service, clock_service,
uuid_service, uuid_service,
sales_person_service,
slot_service,
} }
} }
@ -303,6 +323,61 @@ async fn test_create_with_created_fail() {
); );
} }
#[tokio::test]
async fn test_create_sales_person_does_not_exist() {
let mut deps = build_dependencies(true, "hr");
deps.sales_person_service.checkpoint();
deps.sales_person_service
.expect_exists()
.with(eq(default_sales_person_id()), eq(()))
.returning(|_, _| Ok(false));
let service = deps.build_service();
let result = service
.create(
&Booking {
id: Uuid::nil(),
version: Uuid::nil(),
created: None,
..default_booking()
},
(),
)
.await;
dbg!(&result);
test_validation_error(
&result,
&ValidationFailureItem::IdDoesNotExist("sales_person_id".into(), default_sales_person_id()),
1,
);
}
#[tokio::test]
async fn test_create_slot_does_not_exist() {
let mut deps = build_dependencies(true, "hr");
deps.slot_service.checkpoint();
deps.slot_service
.expect_exists()
.with(eq(default_slot_id()), eq(()))
.returning(|_, _| Ok(false));
let service = deps.build_service();
let result = service
.create(
&Booking {
id: Uuid::nil(),
version: Uuid::nil(),
created: None,
..default_booking()
},
(),
)
.await;
test_validation_error(
&result,
&ValidationFailureItem::IdDoesNotExist("slot_id".into(), default_slot_id()),
1,
);
}
#[tokio::test] #[tokio::test]
async fn test_delete_no_permission() { async fn test_delete_no_permission() {
let deps = build_dependencies(false, "hr"); let deps = build_dependencies(false, "hr");

View file

@ -517,3 +517,26 @@ async fn test_delete_not_found() {
let result = sales_person_service.delete(default_id(), ()).await; let result = sales_person_service.delete(default_id(), ()).await;
test_not_found(&result, &default_id()); test_not_found(&result, &default_id());
} }
#[tokio::test]
async fn test_exists() {
let mut dependencies = build_dependencies(true, "hr");
dependencies
.sales_person_dao
.expect_find_by_id()
.with(eq(default_id()))
.returning(|_| Ok(Some(default_sales_person_entity())));
let sales_person_service = dependencies.build_service();
let result = sales_person_service.exists(default_id(), ()).await.unwrap();
assert!(result);
let mut dependencies = build_dependencies(true, "hr");
dependencies.sales_person_dao.checkpoint();
dependencies
.sales_person_dao
.expect_find_by_id()
.with(eq(default_id()))
.returning(|_| Ok(None));
let result = sales_person_service.exists(default_id(), ()).await.unwrap();
assert_eq!(false, !result);
}