bitpill 0.3.3

A personal medication management TUI application built in Rust.
Documentation
use std::convert::TryFrom;
use std::sync::Arc;

use crate::application::{
    dtos::{requests::CreateMedicationRequest, responses::CreateMedicationResponse},
    errors::ApplicationError,
    ports::{
        create_medication_port::CreateMedicationPort,
        outbound::medication_repository_port::MedicationRepository,
    },
};
use crate::domain::entities::medication::Medication;

pub struct CreateMedicationService {
    repository: Arc<dyn MedicationRepository>,
}

impl CreateMedicationService {
    pub fn new(repository: Arc<dyn MedicationRepository>) -> Self {
        Self { repository }
    }
}

impl CreateMedicationPort for CreateMedicationService {
    fn execute(
        &self,
        request: CreateMedicationRequest,
    ) -> Result<CreateMedicationResponse, ApplicationError> {
        let medication = Medication::try_from(request)?;

        self.repository.save(&medication)?;

        Ok(CreateMedicationResponse {
            id: medication.id().to_string(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::application::ports::fakes::FakeMedicationRepository;

    fn make_service(repo: Arc<FakeMedicationRepository>) -> CreateMedicationService {
        CreateMedicationService::new(repo)
    }

    fn valid_request() -> CreateMedicationRequest {
        CreateMedicationRequest::new("Aspirin", 500, vec![(8, 0)], "OnceDaily")
    }

    #[test]
    fn execute_with_valid_request_returns_non_empty_id() {
        let repo = Arc::new(FakeMedicationRepository::new());
        let service = make_service(repo);

        let result = service.execute(valid_request());

        assert!(result.is_ok());
        assert!(!result.unwrap().id.is_empty());
    }

    #[test]
    fn execute_with_valid_request_saves_to_repository() {
        let repo = Arc::new(FakeMedicationRepository::new());
        let service = make_service(repo.clone());

        service.execute(valid_request()).unwrap();

        assert_eq!(repo.saved_count(), 1);
    }

    #[test]
    fn execute_with_empty_name_returns_domain_error() {
        let repo = Arc::new(FakeMedicationRepository::new());
        let service = make_service(repo);
        let request = CreateMedicationRequest::new("", 500, vec![(8, 0)], "OnceDaily");

        let result = service.execute(request);

        assert!(matches!(result, Err(ApplicationError::Domain(_))));
    }

    #[test]
    fn execute_with_zero_dosage_returns_domain_error() {
        let repo = Arc::new(FakeMedicationRepository::new());
        let service = make_service(repo);
        let request = CreateMedicationRequest::new("Aspirin", 0, vec![(8, 0)], "OnceDaily");

        let result = service.execute(request);

        assert!(matches!(result, Err(ApplicationError::Domain(_))));
    }

    #[test]
    fn execute_when_repository_fails_returns_storage_error() {
        let repo = Arc::new(FakeMedicationRepository::failing());
        let service = make_service(repo);

        let result = service.execute(valid_request());

        assert!(matches!(result, Err(ApplicationError::Storage(_))));
    }
}