Skip to main content

max / makenotwork

5.8 KB · 202 lines History Blame Raw
1 use crate::harness::TestHarness;
2 use mt_core::types::BanType;
3
4 /// Magic bytes plus padding: enough for `validate_image`'s sniff, and the zeroed
5 /// IHDR reads back as 0x0 so the pixel-bomb cap is not involved.
6 fn png_bytes() -> Vec<u8> {
7 let mut v = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
8 v.extend_from_slice(&[0u8; 64]);
9 v
10 }
11
12 #[tokio::test]
13 async fn upload_requires_login() {
14 let mut h = TestHarness::new().await;
15 // Don't log in, just establish session
16 h.client.get("/").await;
17
18 let resp = h
19 .client
20 .post_multipart("/p/test/upload", b"fake", "image/png", "test.png")
21 .await;
22 // Should be 401 (unauthorized), not 200
23 assert_eq!(
24 resp.status.as_u16(),
25 401,
26 "Upload without login should return 401"
27 );
28 }
29
30 #[tokio::test]
31 async fn upload_returns_503_without_s3() {
32 let mut h = TestHarness::new().await;
33 let user_id = h.login_as("uploader").await;
34 let comm_id = h.create_community("UploadComm", "upload-comm").await;
35 h.add_membership(user_id, comm_id, "member").await;
36
37 let resp = h
38 .client
39 .post_multipart(
40 "/p/upload-comm/upload",
41 b"fake png data",
42 "image/png",
43 "test.png",
44 )
45 .await;
46 assert_eq!(
47 resp.status.as_u16(),
48 503,
49 "Should return 503 when S3 is not configured"
50 );
51 }
52
53 #[tokio::test]
54 async fn serve_image_nonexistent_returns_404() {
55 let mut h = TestHarness::new().await;
56 let fake_id = uuid::Uuid::new_v4();
57 let resp = h.client.get(&format!("/uploads/{fake_id}")).await;
58 assert_eq!(
59 resp.status.as_u16(),
60 404,
61 "Nonexistent image should return 404"
62 );
63 }
64
65 #[tokio::test]
66 async fn serve_image_invalid_uuid_returns_404() {
67 let mut h = TestHarness::new().await;
68 let resp = h.client.get("/uploads/not-a-uuid").await;
69 assert_eq!(resp.status.as_u16(), 404, "Invalid UUID should return 404");
70 }
71
72 /// The external-image proxy is login-gated: an anonymous request is rejected
73 /// before any outbound fetch, so it can't be driven as an open relay (M-UX1).
74 #[tokio::test]
75 async fn image_proxy_requires_login() {
76 let mut h = TestHarness::new().await;
77 h.client.get("/").await; // establish a session, but stay logged out
78
79 let resp = h
80 .client
81 .get("/img-proxy?u=https%3A%2F%2Fexample.com%2Fa.png")
82 .await;
83 assert_eq!(
84 resp.status.as_u16(),
85 401,
86 "anonymous image-proxy request must be rejected before any fetch"
87 );
88 }
89
90 // Upload authz, driven against the in-process S3 stub. Without it these
91 // handlers stop at the `state.s3` 503 guard and never reach the gate.
92
93 /// Baseline: with S3 wired, a member in good standing gets through and the
94 /// object actually lands. The 403 tests below are only meaningful next to this
95 /// one, since a broken stub would make every case fail for the wrong reason.
96 #[tokio::test]
97 async fn upload_stores_object_for_member_in_good_standing() {
98 let mut h = TestHarness::new_with_s3().await;
99 let user_id = h.login_as("okuploader").await;
100 let comm_id = h.create_community("UploadComm", "upload-comm").await;
101 h.add_membership(user_id, comm_id, "member").await;
102
103 let resp = h
104 .client
105 .post_multipart(
106 "/p/upload-comm/upload",
107 &png_bytes(),
108 "image/png",
109 "test.png",
110 )
111 .await;
112 assert_eq!(resp.status.as_u16(), 200, "member upload should succeed");
113
114 let stub = h.s3.as_ref().expect("harness built with S3");
115 assert_eq!(stub.len(), 1, "exactly one object should have been stored");
116
117 let row = sqlx::query_scalar::<_, String>("SELECT s3_key FROM images LIMIT 1")
118 .fetch_one(&h.db)
119 .await
120 .expect("image row should exist");
121 let stored = stub
122 .get(&row)
123 .expect("stored object should match the row key");
124 assert_eq!(stored.data, png_bytes(), "stored bytes should round-trip");
125 assert_eq!(stored.content_type, "image/png");
126 }
127
128 #[tokio::test]
129 async fn platform_suspended_user_cannot_upload() {
130 let mut h = TestHarness::new_with_s3().await;
131 let user_id = h.login_as("suspuploader").await;
132 let comm_id = h.create_community("UploadComm", "upload-comm").await;
133 h.add_membership(user_id, comm_id, "member").await;
134
135 mt_db::mutations::suspend_user(&h.db, user_id, Some("test"))
136 .await
137 .unwrap();
138
139 let resp = h
140 .client
141 .post_multipart(
142 "/p/upload-comm/upload",
143 &png_bytes(),
144 "image/png",
145 "test.png",
146 )
147 .await;
148 assert_eq!(
149 resp.status.as_u16(),
150 403,
151 "a platform-suspended user must not be able to upload"
152 );
153 assert_eq!(
154 h.s3.as_ref().expect("harness built with S3").len(),
155 0,
156 "no object should have been stored"
157 );
158 }
159
160 #[tokio::test]
161 async fn muted_user_cannot_upload() {
162 let mut h = TestHarness::new_with_s3().await;
163 let owner_id = h.login_as("uploadowner").await;
164 let comm_id = h.create_community("UploadComm", "upload-comm").await;
165 h.add_membership(owner_id, comm_id, "owner").await;
166
167 let user_id = h.login_as("muteuploader").await;
168 h.add_membership(user_id, comm_id, "member").await;
169
170 mt_db::mutations::create_community_ban(
171 &h.db,
172 comm_id,
173 user_id,
174 owner_id,
175 BanType::Mute,
176 Some("test"),
177 None,
178 )
179 .await
180 .unwrap();
181
182 let resp = h
183 .client
184 .post_multipart(
185 "/p/upload-comm/upload",
186 &png_bytes(),
187 "image/png",
188 "test.png",
189 )
190 .await;
191 assert_eq!(
192 resp.status.as_u16(),
193 403,
194 "a muted user must not be able to upload"
195 );
196 assert_eq!(
197 h.s3.as_ref().expect("harness built with S3").len(),
198 0,
199 "no object should have been stored"
200 );
201 }
202