use actix_web::{web, HttpResponse}; use diesel::prelude::*; use crate::{ auth::extractor::AuthUser, database::establish_connection, schema::{feed, feed_item}, }; pub async fn delete_feed(path: web::Path, auth_user: AuthUser) -> HttpResponse { let feed_id = path.into_inner(); let mut connection = establish_connection(); let owner: Option = feed::table .find(feed_id) .select(feed::user_id) .first(&mut connection) .optional() .unwrap_or(None); // Treat "doesn't exist" and "not yours" the same, so callers can't probe // for other users' feed ids. if owner != Some(auth_user.0) { return HttpResponse::NotFound().finish(); } diesel::delete(feed_item::table.filter(feed_item::feed_id.eq(feed_id))) .execute(&mut connection) .ok(); diesel::delete(feed::table.filter(feed::id.eq(feed_id))) .execute(&mut connection) .ok(); HttpResponse::NoContent().finish() } #[cfg(test)] mod tests { use actix_service::Service; use actix_web::http::StatusCode; use actix_web::{test, web, App, HttpMessage}; use diesel::prelude::*; use super::delete_feed; use crate::auth::extractor::AuthUser; use crate::database::establish_connection; use crate::schema::{feed, feed_item}; use crate::test_helpers::{ delete_feed as cleanup_feed, delete_user, insert_feed, insert_feed_item, insert_user, }; #[actix_web::test] async fn delete_feed_removes_feed_and_items() { let mut connection = establish_connection(); let user = insert_user(&mut connection, "secret"); let f = insert_feed(&mut connection, user.id); let item = insert_feed_item(&mut connection, f.id, false); let user_id = user.id; let app = test::init_service( App::new() .wrap_fn(move |req, srv| { req.extensions_mut().insert(AuthUser(user_id)); srv.call(req) }) .route("/feed/{feed_id}", web::delete().to(delete_feed)), ) .await; let req = test::TestRequest::delete() .uri(&format!("/feed/{}", f.id)) .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(StatusCode::NO_CONTENT, resp.status()); let feed_exists: i64 = feed::table .filter(feed::id.eq(f.id)) .count() .get_result(&mut connection) .unwrap(); assert_eq!(0, feed_exists); let item_exists: i64 = feed_item::table .filter(feed_item::id.eq(item.id)) .count() .get_result(&mut connection) .unwrap(); assert_eq!(0, item_exists); delete_user(&mut connection, user.id); } #[actix_web::test] async fn delete_feed_returns_404_for_nonexistent_feed() { let app = test::init_service( App::new() .wrap_fn(move |req, srv| { req.extensions_mut().insert(AuthUser(1)); srv.call(req) }) .route("/feed/{feed_id}", web::delete().to(delete_feed)), ) .await; let req = test::TestRequest::delete() .uri("/feed/999999999") .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(StatusCode::NOT_FOUND, resp.status()); } #[actix_web::test] async fn delete_feed_does_not_affect_other_feeds() { let mut connection = establish_connection(); let user = insert_user(&mut connection, "secret"); let feed_a = insert_feed(&mut connection, user.id); let feed_b = insert_feed(&mut connection, user.id); let user_id = user.id; let app = test::init_service( App::new() .wrap_fn(move |req, srv| { req.extensions_mut().insert(AuthUser(user_id)); srv.call(req) }) .route("/feed/{feed_id}", web::delete().to(delete_feed)), ) .await; let req = test::TestRequest::delete() .uri(&format!("/feed/{}", feed_a.id)) .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(StatusCode::NO_CONTENT, resp.status()); let feed_b_exists: i64 = feed::table .filter(feed::id.eq(feed_b.id)) .count() .get_result(&mut connection) .unwrap(); assert_eq!(1, feed_b_exists); cleanup_feed(&mut connection, feed_b.id); delete_user(&mut connection, user.id); } #[actix_web::test] async fn delete_feed_rejects_other_users_feed() { let mut connection = establish_connection(); let user_a = insert_user(&mut connection, "secret"); let user_b = insert_user(&mut connection, "secret"); let feed_b = insert_feed(&mut connection, user_b.id); let user_a_id = user_a.id; let app = test::init_service( App::new() .wrap_fn(move |req, srv| { req.extensions_mut().insert(AuthUser(user_a_id)); srv.call(req) }) .route("/feed/{feed_id}", web::delete().to(delete_feed)), ) .await; let req = test::TestRequest::delete() .uri(&format!("/feed/{}", feed_b.id)) .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(StatusCode::NOT_FOUND, resp.status()); let feed_b_exists: i64 = feed::table .filter(feed::id.eq(feed_b.id)) .count() .get_result(&mut connection) .unwrap(); assert_eq!(1, feed_b_exists); cleanup_feed(&mut connection, feed_b.id); delete_user(&mut connection, user_a.id); delete_user(&mut connection, user_b.id); } }