From 6155772be38d5fe2e673ef96c0f9e7ce8f252e17 Mon Sep 17 00:00:00 2001 From: Andre Heber Date: Sat, 16 Nov 2024 07:49:52 +0100 Subject: [PATCH] implement StoreTokenError --- src/routes/subscriptions.rs | 29 +++++++++++++++++++---------- tests/api/subscriptions.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/routes/subscriptions.rs b/src/routes/subscriptions.rs index ba42a66..b1745d8 100644 --- a/src/routes/subscriptions.rs +++ b/src/routes/subscriptions.rs @@ -1,4 +1,4 @@ -use actix_web::{web, HttpResponse}; +use actix_web::{web, HttpResponse, ResponseError}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serde::Deserialize; use chrono::Utc; @@ -10,6 +10,17 @@ use lettre::{ use crate::{domain::{NewSubscriber, SubscriberEmail, SubscriberName}, email_client::EmailClient, startup::ApplicationBaseUrl}; +#[derive(Debug)] +pub struct StoreTokenError(sqlx::Error); + +impl std::fmt::Display for StoreTokenError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "A database error was encountered while trying to store the subscription token.") + } +} + +impl ResponseError for StoreTokenError {} + #[derive(Deserialize)] pub struct FormData { pub email: String, @@ -29,23 +40,21 @@ pub async fn subscribe( connection_pool: web::Data>, email_client: web::Data, base_url: web::Data, -) -> HttpResponse { +) -> Result { let new_subscriber = match form.0.try_into() { Ok(subscriber) => subscriber, - Err(_) => return HttpResponse::BadRequest().finish(), + Err(e) => return Err(actix_web::error::ErrorBadRequest(e)), }; let mut transaction = match connection_pool.begin().await { Ok(transaction) => transaction, - Err(_) => return HttpResponse::InternalServerError().finish(), + Err(_) => return Err(actix_web::error::ErrorInternalServerError("Failed to acquire a database connection.")), }; let subscriber_id = match insert_subscriber(&new_subscriber, &mut transaction).await { Ok(subscriber_id) => subscriber_id, - Err(_) => return HttpResponse::InternalServerError().finish(), + Err(_) => return Err(actix_web::error::ErrorInternalServerError("Failed to save new subscriber details.")), }; let subscription_token = generate_confirmation_token(); - if store_token(&mut transaction, &subscriber_id, &subscription_token).await.is_err() { - return HttpResponse::InternalServerError().finish(); - } + store_token(&mut transaction, &subscriber_id, &subscription_token).await?; if transaction.commit().await.is_err() { return HttpResponse::InternalServerError().finish(); } @@ -173,7 +182,7 @@ async fn insert_subscriber(new_subscriber: &NewSubscriber, transaction: &mut Tra name = "Storing subscription token in the database", skip(transaction, subscriber_id, subscription_token) )] -async fn store_token(transaction: &mut Transaction<'_, Postgres>, subscriber_id: &Uuid, subscription_token: &str) -> Result<(), sqlx::Error> { +async fn store_token(transaction: &mut Transaction<'_, Postgres>, subscriber_id: &Uuid, subscription_token: &str) -> Result<(), StoreTokenError> { query!( r#" INSERT INTO subscription_tokens (subscription_token, subscriber_id) @@ -186,7 +195,7 @@ async fn store_token(transaction: &mut Transaction<'_, Postgres>, subscriber_id: .await .map_err(|e| { tracing::error!("Failed to execute query: {:?}", e); - e + StoreTokenError(e) })?; Ok(()) } diff --git a/tests/api/subscriptions.rs b/tests/api/subscriptions.rs index f9b832e..2ae3b8e 100644 --- a/tests/api/subscriptions.rs +++ b/tests/api/subscriptions.rs @@ -118,4 +118,35 @@ async fn subscribe_sends_a_confirmation_email_with_a_link() { // The two links should be identical assert_eq!(confirmation_links.html, confirmation_links.plain_text); +} + +#[tokio::test] +async fn subscribe_fails_if_there_is_a_fatal_database_error() { + let app = spawn_app().await; + let body = "name=Andre%20Heber&email=andre.heber%40gmx.net"; + + // Mock::given(path("/email")) + // .and(method(Method::POST)) + // .respond_with(ResponseTemplate::new(200)) + // .expect(1) + // .mount(&app.email_server) + // .await; + + // // Let's cause a database error + // let (subscribers, pool) = app.get_subscribers().await; + sqlx::query!("ALTER TABLE subscription_tokens DROP COLUMN subscription_token;",) + .execute(&app.connection_pool) + .await + .expect("Failed to drop the subscriptions table"); + + let response = app.post_subscriptions(body.to_string()).await; + assert_eq!(response.status().as_u16(), 500); + + // Bring the table back for other tests + // sqlx::query(include_str!("../../../migrations/redo_subscriptions_table.sql")) + // .execute(&pool) + // .await + // .expect("Failed to re-create the subscriptions table"); + + // drop(subscribers); } \ No newline at end of file