entertainarr-adapter-http 0.1.1

HTTP adapter for entertainarr
Documentation
use axum::Json;
use axum::extract::{Path, State};
use entertainarr_domain::podcast::entity::PodcastSubscriptionUpdate;
use entertainarr_domain::podcast::prelude::PodcastService;

use crate::entity::ApiResource;
use crate::entity::podcast_subscription::PodcastSubscriptionUpdateDocument;
use crate::server::extractor::user::CurrentUser;
use crate::server::handler::error::ApiErrorResponse;

pub async fn handle<S>(
    State(state): State<S>,
    CurrentUser(user_id): CurrentUser,
    Path(podcast_id): Path<u64>,
    Json(payload): Json<ApiResource<PodcastSubscriptionUpdateDocument>>,
) -> Result<axum::http::StatusCode, ApiErrorResponse>
where
    S: crate::server::prelude::ServerState,
{
    state
        .podcast_service()
        .update_subscription_by_id(
            user_id,
            podcast_id,
            PodcastSubscriptionUpdate {
                min_duration: payload.data.attributes.min_duration,
                max_duration: payload.data.attributes.max_duration,
            },
        )
        .await
        .map(|_| axum::http::StatusCode::NO_CONTENT)
        .map_err(|err| {
            tracing::error!(error = ?err, "unable to update subscription");
            ApiErrorResponse::internal()
        })
}

#[cfg(test)]
mod tests {
    use axum::Json;
    use axum::extract::{Path, State};
    use axum::http::StatusCode;
    use entertainarr_domain::podcast::prelude::MockPodcastService;

    use crate::entity::podcast_subscription::{
        PodcastSubscriptionUpdateAttributes, PodcastSubscriptionUpdateDocument,
    };
    use crate::entity::{ApiResource, Couple};
    use crate::server::extractor::user::CurrentUser;
    use crate::server::prelude::tests::MockServerState;

    #[tokio::test]
    async fn should_fail_if_service_fails() {
        let mut podcast_service = MockPodcastService::new();
        podcast_service
            .expect_update_subscription_by_id()
            .return_once(|user_id, podcast_id, _feed_url| {
                assert_eq!(user_id, 1);
                assert_eq!(podcast_id, 2);
                Box::pin(async move { Err(anyhow::anyhow!("oops")) })
            });
        let state = MockServerState::builder().podcast(podcast_service).build();
        let err = super::handle(
            State(state),
            CurrentUser(1),
            Path(2),
            Json(ApiResource::new(PodcastSubscriptionUpdateDocument {
                id: Couple(2, 1),
                kind: Default::default(),
                attributes: PodcastSubscriptionUpdateAttributes {
                    min_duration: Some(12),
                    max_duration: None,
                },
            })),
        )
        .await
        .unwrap_err();
        assert_eq!(err.status_code, StatusCode::INTERNAL_SERVER_ERROR);
    }

    #[tokio::test]
    async fn should_succeed() {
        let mut podcast_service = MockPodcastService::new();
        podcast_service
            .expect_update_subscription_by_id()
            .return_once(|user_id, podcast_id, _| {
                assert_eq!(user_id, 1);
                assert_eq!(podcast_id, 2);
                Box::pin(async move { Ok(()) })
            });
        let state = MockServerState::builder().podcast(podcast_service).build();
        let res = super::handle(
            State(state),
            CurrentUser(1),
            Path(2),
            Json(ApiResource::new(PodcastSubscriptionUpdateDocument {
                id: Couple(2, 1),
                kind: Default::default(),
                attributes: PodcastSubscriptionUpdateAttributes {
                    min_duration: Some(12),
                    max_duration: None,
                },
            })),
        )
        .await
        .unwrap();
        assert_eq!(res, StatusCode::NO_CONTENT);
    }
}