use crate::harness::TestHarness; use mt_core::types::BanType; /// Magic bytes plus padding: enough for `validate_image`'s sniff, and the zeroed /// IHDR reads back as 0x0 so the pixel-bomb cap is not involved. fn png_bytes() -> Vec { let mut v = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; v.extend_from_slice(&[0u8; 64]); v } #[tokio::test] async fn upload_requires_login() { let mut h = TestHarness::new().await; // Don't log in, just establish session h.client.get("/").await; let resp = h .client .post_multipart("/p/test/upload", b"fake", "image/png", "test.png") .await; // Should be 401 (unauthorized), not 200 assert_eq!( resp.status.as_u16(), 401, "Upload without login should return 401" ); } #[tokio::test] async fn upload_returns_503_without_s3() { let mut h = TestHarness::new().await; let user_id = h.login_as("uploader").await; let comm_id = h.create_community("UploadComm", "upload-comm").await; h.add_membership(user_id, comm_id, "member").await; let resp = h .client .post_multipart( "/p/upload-comm/upload", b"fake png data", "image/png", "test.png", ) .await; assert_eq!( resp.status.as_u16(), 503, "Should return 503 when S3 is not configured" ); } #[tokio::test] async fn serve_image_nonexistent_returns_404() { let mut h = TestHarness::new().await; let fake_id = uuid::Uuid::new_v4(); let resp = h.client.get(&format!("/uploads/{fake_id}")).await; assert_eq!( resp.status.as_u16(), 404, "Nonexistent image should return 404" ); } #[tokio::test] async fn serve_image_invalid_uuid_returns_404() { let mut h = TestHarness::new().await; let resp = h.client.get("/uploads/not-a-uuid").await; assert_eq!(resp.status.as_u16(), 404, "Invalid UUID should return 404"); } /// The external-image proxy is login-gated: an anonymous request is rejected /// before any outbound fetch, so it can't be driven as an open relay (M-UX1). #[tokio::test] async fn image_proxy_requires_login() { let mut h = TestHarness::new().await; h.client.get("/").await; // establish a session, but stay logged out let resp = h .client .get("/img-proxy?u=https%3A%2F%2Fexample.com%2Fa.png") .await; assert_eq!( resp.status.as_u16(), 401, "anonymous image-proxy request must be rejected before any fetch" ); } // Upload authz, driven against the in-process S3 stub. Without it these // handlers stop at the `state.s3` 503 guard and never reach the gate. /// Baseline: with S3 wired, a member in good standing gets through and the /// object actually lands. The 403 tests below are only meaningful next to this /// one, since a broken stub would make every case fail for the wrong reason. #[tokio::test] async fn upload_stores_object_for_member_in_good_standing() { let mut h = TestHarness::new_with_s3().await; let user_id = h.login_as("okuploader").await; let comm_id = h.create_community("UploadComm", "upload-comm").await; h.add_membership(user_id, comm_id, "member").await; let resp = h .client .post_multipart( "/p/upload-comm/upload", &png_bytes(), "image/png", "test.png", ) .await; assert_eq!(resp.status.as_u16(), 200, "member upload should succeed"); let stub = h.s3.as_ref().expect("harness built with S3"); assert_eq!(stub.len(), 1, "exactly one object should have been stored"); let row = sqlx::query_scalar::<_, String>("SELECT s3_key FROM images LIMIT 1") .fetch_one(&h.db) .await .expect("image row should exist"); let stored = stub .get(&row) .expect("stored object should match the row key"); assert_eq!(stored.data, png_bytes(), "stored bytes should round-trip"); assert_eq!(stored.content_type, "image/png"); } #[tokio::test] async fn platform_suspended_user_cannot_upload() { let mut h = TestHarness::new_with_s3().await; let user_id = h.login_as("suspuploader").await; let comm_id = h.create_community("UploadComm", "upload-comm").await; h.add_membership(user_id, comm_id, "member").await; mt_db::mutations::suspend_user(&h.db, user_id, Some("test")) .await .unwrap(); let resp = h .client .post_multipart( "/p/upload-comm/upload", &png_bytes(), "image/png", "test.png", ) .await; assert_eq!( resp.status.as_u16(), 403, "a platform-suspended user must not be able to upload" ); assert_eq!( h.s3.as_ref().expect("harness built with S3").len(), 0, "no object should have been stored" ); } #[tokio::test] async fn muted_user_cannot_upload() { let mut h = TestHarness::new_with_s3().await; let owner_id = h.login_as("uploadowner").await; let comm_id = h.create_community("UploadComm", "upload-comm").await; h.add_membership(owner_id, comm_id, "owner").await; let user_id = h.login_as("muteuploader").await; h.add_membership(user_id, comm_id, "member").await; mt_db::mutations::create_community_ban( &h.db, comm_id, user_id, owner_id, BanType::Mute, Some("test"), None, ) .await .unwrap(); let resp = h .client .post_multipart( "/p/upload-comm/upload", &png_bytes(), "image/png", "test.png", ) .await; assert_eq!( resp.status.as_u16(), 403, "a muted user must not be able to upload" ); assert_eq!( h.s3.as_ref().expect("harness built with S3").len(), 0, "no object should have been stored" ); }