use crate::harness::TestHarness; #[tokio::test] async fn endorse_post_happy_path() { let mut h = TestHarness::new().await; let author_id = h.login_as("author").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(author_id, comm_id, "member").await; let thread_id = h .create_thread_with_post(cat_id, author_id, "Good Post", "Content") .await; let posts = mt_db::queries::list_posts_in_thread(&h.db, thread_id) .await .unwrap(); let post_id = posts[0].id; // Login as a different user let endorser_id = h.login_as("endorser").await; h.add_membership(endorser_id, comm_id, "member").await; // GET thread page for CSRF let thread_url = format!("/p/test/general/{}", thread_id); h.client.get(&thread_url).await; let endorse_url = format!("/p/test/general/{}/posts/{}/endorse", thread_id, post_id); let resp = h.client.post_form(&endorse_url, "").await; assert!( resp.status.is_redirection(), "Expected redirect after endorse, got {}", resp.status ); // Verify DB row exists let endorsements = mt_db::queries::list_endorsements_for_posts(&h.db, &[post_id]) .await .unwrap(); assert_eq!(endorsements.len(), 1); assert_eq!(endorsements[0].endorser_id, endorser_id); } #[tokio::test] async fn toggle_endorsement_removes() { let mut h = TestHarness::new().await; let author_id = h.login_as("author2").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(author_id, comm_id, "member").await; let thread_id = h .create_thread_with_post(cat_id, author_id, "Toggle Post", "Content") .await; let posts = mt_db::queries::list_posts_in_thread(&h.db, thread_id) .await .unwrap(); let post_id = posts[0].id; let toggler_id = h.login_as("toggler").await; h.add_membership(toggler_id, comm_id, "member").await; let thread_url = format!("/p/test/general/{}", thread_id); h.client.get(&thread_url).await; let endorse_url = format!("/p/test/general/{}/posts/{}/endorse", thread_id, post_id); // First endorse h.client.post_form(&endorse_url, "").await; let endorsements = mt_db::queries::list_endorsements_for_posts(&h.db, &[post_id]) .await .unwrap(); assert_eq!(endorsements.len(), 1, "Should have 1 endorsement after first toggle"); // GET again for fresh CSRF h.client.get(&thread_url).await; // Second endorse (un-endorse) h.client.post_form(&endorse_url, "").await; let endorsements = mt_db::queries::list_endorsements_for_posts(&h.db, &[post_id]) .await .unwrap(); assert_eq!(endorsements.len(), 0, "Should have 0 endorsements after second toggle"); } #[tokio::test] async fn self_endorse_rejected() { let mut h = TestHarness::new().await; let author_id = h.login_as("selfendorser").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(author_id, comm_id, "member").await; let thread_id = h .create_thread_with_post(cat_id, author_id, "My Post", "Content") .await; let posts = mt_db::queries::list_posts_in_thread(&h.db, thread_id) .await .unwrap(); let post_id = posts[0].id; // GET thread page for CSRF let thread_url = format!("/p/test/general/{}", thread_id); h.client.get(&thread_url).await; let endorse_url = format!("/p/test/general/{}/posts/{}/endorse", thread_id, post_id); let resp = h.client.post_form(&endorse_url, "").await; assert_eq!(resp.status.as_u16(), 403, "Self-endorsement should be 403"); } #[tokio::test] async fn endorse_requires_login() { let mut h = TestHarness::new().await; let author_id = h.login_as("loginauthor").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(author_id, comm_id, "member").await; let thread_id = h .create_thread_with_post(cat_id, author_id, "Login Test", "Content") .await; let posts = mt_db::queries::list_posts_in_thread(&h.db, thread_id) .await .unwrap(); let post_id = posts[0].id; // Login as a different user, then logout by creating a fresh harness client // Actually, just don't login — create a fresh harness without login let mut h2 = TestHarness::new().await; // GET some page for CSRF h2.client.get("/").await; let endorse_url = format!("/p/test/general/{}/posts/{}/endorse", thread_id, post_id); let resp = h2.client.post_form(&endorse_url, "").await; assert!( resp.status.is_redirection(), "Expected redirect to login, got {}", resp.status ); } #[tokio::test] async fn endorse_removed_post_rejected() { let mut h = TestHarness::new().await; let author_id = h.login_as("removedauthor").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(author_id, comm_id, "member").await; let thread_id = h .create_thread_with_post(cat_id, author_id, "Removed Post", "Content") .await; let posts = mt_db::queries::list_posts_in_thread(&h.db, thread_id) .await .unwrap(); let post_id = posts[0].id; // Mod-remove the post mt_db::mutations::mod_remove_post(&h.db, post_id, author_id) .await .unwrap(); let endorser_id = h.login_as("removedendorser").await; h.add_membership(endorser_id, comm_id, "member").await; let thread_url = format!("/p/test/general/{}", thread_id); h.client.get(&thread_url).await; let endorse_url = format!("/p/test/general/{}/posts/{}/endorse", thread_id, post_id); let resp = h.client.post_form(&endorse_url, "").await; assert_eq!(resp.status.as_u16(), 403, "Endorsing removed post should be 403"); } #[tokio::test] async fn endorsement_count_visible_to_author() { let mut h = TestHarness::new().await; let author_id = h.login_as("countauthor").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(author_id, comm_id, "member").await; let thread_id = h .create_thread_with_post(cat_id, author_id, "Count Test", "Content") .await; let posts = mt_db::queries::list_posts_in_thread(&h.db, thread_id) .await .unwrap(); let post_id = posts[0].id; // Insert endorsement directly via DB (avoids needing to switch users) let endorser_id = uuid::Uuid::new_v4(); sqlx::query("INSERT INTO users (mnw_account_id, username, display_name) VALUES ($1, $2, $2)") .bind(endorser_id) .bind("countendorser") .execute(&h.db) .await .unwrap(); mt_db::mutations::toggle_endorsement(&h.db, post_id, endorser_id) .await .unwrap(); // Author is still logged in — view thread let thread_url = format!("/p/test/general/{}", thread_id); let resp = h.client.get(&thread_url).await; assert!( resp.text.contains("endorsement-count"), "Author should see endorsement count" ); } #[tokio::test] async fn endorsement_count_visible_to_mod() { let mut h = TestHarness::new().await; let author_id = h.login_as("modcountauthor").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(author_id, comm_id, "member").await; let thread_id = h .create_thread_with_post(cat_id, author_id, "Mod Count Test", "Content") .await; let posts = mt_db::queries::list_posts_in_thread(&h.db, thread_id) .await .unwrap(); let post_id = posts[0].id; // Have someone endorse via DB let endorser_id = h.login_as("modcountendorser").await; h.add_membership(endorser_id, comm_id, "member").await; mt_db::mutations::toggle_endorsement(&h.db, post_id, endorser_id) .await .unwrap(); // Login as moderator and view thread let mod_id = h.login_as("modviewer").await; h.add_membership(mod_id, comm_id, "moderator").await; let thread_url = format!("/p/test/general/{}", thread_id); let resp = h.client.get(&thread_url).await; assert!( resp.text.contains("endorsement-count"), "Mod should see endorsement count" ); } #[tokio::test] async fn endorsement_count_hidden_from_others() { let mut h = TestHarness::new().await; let author_id = h.login_as("hiddenauthor").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(author_id, comm_id, "member").await; let thread_id = h .create_thread_with_post(cat_id, author_id, "Hidden Count", "Content") .await; let posts = mt_db::queries::list_posts_in_thread(&h.db, thread_id) .await .unwrap(); let post_id = posts[0].id; // Have someone endorse via DB let endorser_id = h.login_as("hiddenendorser").await; h.add_membership(endorser_id, comm_id, "member").await; mt_db::mutations::toggle_endorsement(&h.db, post_id, endorser_id) .await .unwrap(); // Login as a third user (not author, not endorser, not mod) let _bystander_id = h.login_as("bystander").await; h.add_membership(_bystander_id, comm_id, "member").await; let thread_url = format!("/p/test/general/{}", thread_id); let resp = h.client.get(&thread_url).await; assert!( !resp.text.contains("endorsement-count"), "Bystander should NOT see endorsement count" ); }