Skip to main content

max / makenotwork

7.0 KB · 255 lines History Blame Raw
1 //! Video upload and playback integration tests.
2
3 use crate::harness::TestHarness;
4 use serde_json::{json, Value};
5
6 /// Helper: create a trusted creator with a video item.
7 async fn setup_creator_with_video(
8 h: &mut TestHarness,
9 price_cents: i64,
10 ) -> (String, String, String) {
11 let setup = h
12 .create_creator_with_item("creator", "video", price_cents)
13 .await;
14 h.trust_user(setup.user_id).await;
15 h.grant_tier(setup.user_id, "big_files").await;
16 (
17 setup.user_id.to_string(),
18 setup.project_id,
19 setup.item_id,
20 )
21 }
22
23 #[tokio::test]
24 async fn video_presign_upload() {
25 let mut h = TestHarness::with_storage().await;
26 let (_, _, item_id) = setup_creator_with_video(&mut h, 0).await;
27
28 let body = json!({
29 "item_id": item_id,
30 "file_type": "video",
31 "file_name": "demo.mp4",
32 "content_type": "video/mp4",
33 });
34 let resp = h
35 .client
36 .post_json("/api/upload/presign", &body.to_string())
37 .await;
38 assert!(resp.status.is_success(), "Presign failed: {}", resp.text);
39
40 let data: Value = resp.json();
41 assert!(data["upload_url"]
42 .as_str()
43 .unwrap()
44 .starts_with("http://test-storage/"));
45 assert!(data["s3_key"]
46 .as_str()
47 .unwrap()
48 .contains("/video/demo.mp4"));
49 assert_eq!(data["expires_in"], 3600);
50 }
51
52 #[tokio::test]
53 async fn video_confirm_upload_updates_db() {
54 let mut h = TestHarness::with_storage().await;
55 let (_, _, item_id) = setup_creator_with_video(&mut h, 0).await;
56
57 // Presign
58 let body = json!({
59 "item_id": item_id,
60 "file_type": "video",
61 "file_name": "clip.mp4",
62 "content_type": "video/mp4",
63 });
64 let resp = h
65 .client
66 .post_json("/api/upload/presign", &body.to_string())
67 .await;
68 assert!(resp.status.is_success());
69 let data: Value = resp.json();
70 let s3_key = data["s3_key"].as_str().unwrap().to_string();
71
72 // Simulate client uploading to S3
73 h.storage
74 .as_ref()
75 .unwrap()
76 .put(&s3_key, b"fake mp4 bytes".to_vec());
77
78 // Confirm
79 let body = json!({
80 "item_id": item_id,
81 "file_type": "video",
82 "s3_key": s3_key,
83 });
84 let resp = h
85 .client
86 .post_json("/api/upload/confirm", &body.to_string())
87 .await;
88 assert!(resp.status.is_success(), "Confirm failed: {}", resp.text);
89
90 // Verify database
91 let db_key: Option<String> =
92 sqlx::query_scalar("SELECT video_s3_key FROM items WHERE id = $1::uuid")
93 .bind(&item_id)
94 .fetch_one(&h.db)
95 .await
96 .unwrap();
97 assert_eq!(db_key.as_deref(), Some(s3_key.as_str()));
98 }
99
100 #[tokio::test]
101 async fn video_stream_free_item() {
102 let mut h = TestHarness::with_storage().await;
103 let (_, project_id, item_id) = setup_creator_with_video(&mut h, 0).await;
104
105 // Set up video key directly in DB
106 let s3_key = format!("test/{}/video/clip.mp4", item_id);
107 sqlx::query("UPDATE items SET video_s3_key = $1, scan_status = 'clean' WHERE id = $2::uuid")
108 .bind(&s3_key)
109 .bind(&item_id)
110 .execute(&h.db)
111 .await
112 .unwrap();
113
114 h.storage
115 .as_ref()
116 .unwrap()
117 .put(&s3_key, b"video data".to_vec());
118
119 // Publish
120 h.client
121 .put_form(&format!("/api/items/{}", item_id), "is_public=true")
122 .await;
123 h.client
124 .put_json(
125 &format!("/api/projects/{}", project_id),
126 r#"{"is_public": true}"#,
127 )
128 .await;
129
130 // Stream
131 let resp = h
132 .client
133 .get(&format!("/api/stream/{}", item_id))
134 .await;
135 assert!(resp.status.is_success(), "Stream failed: {}", resp.text);
136 let data: Value = resp.json();
137 assert!(data["stream_url"]
138 .as_str()
139 .unwrap()
140 .starts_with("http://test-storage/"));
141 }
142
143 #[tokio::test]
144 async fn video_stream_requires_purchase() {
145 let mut h = TestHarness::with_storage().await;
146 let (_, project_id, item_id) = setup_creator_with_video(&mut h, 500).await;
147
148 // Set up video key
149 let s3_key = format!("test/{}/video/clip.mp4", item_id);
150 sqlx::query("UPDATE items SET video_s3_key = $1, scan_status = 'clean' WHERE id = $2::uuid")
151 .bind(&s3_key)
152 .bind(&item_id)
153 .execute(&h.db)
154 .await
155 .unwrap();
156 h.storage
157 .as_ref()
158 .unwrap()
159 .put(&s3_key, b"video data".to_vec());
160
161 // Publish
162 h.client
163 .put_form(&format!("/api/items/{}", item_id), "is_public=true")
164 .await;
165 h.client
166 .put_json(
167 &format!("/api/projects/{}", project_id),
168 r#"{"is_public": true}"#,
169 )
170 .await;
171
172 // Log out and sign up buyer
173 h.client.post_form("/logout", "").await;
174 h.signup("buyer", "buyer@test.com", "password123").await;
175 h.login("buyer", "password123").await;
176
177 // Stream should be forbidden
178 let resp = h
179 .client
180 .get(&format!("/api/stream/{}", item_id))
181 .await;
182 assert_eq!(
183 resp.status.as_u16(),
184 403,
185 "Expected 403, got: {}",
186 resp.text
187 );
188 }
189
190 #[tokio::test]
191 async fn video_storage_tracking() {
192 let mut h = TestHarness::with_storage().await;
193 let (user_id, _, item_id) = setup_creator_with_video(&mut h, 0).await;
194
195 // Presign + upload + confirm
196 let body = json!({
197 "item_id": item_id,
198 "file_type": "video",
199 "file_name": "large.mp4",
200 "content_type": "video/mp4",
201 });
202 let resp = h
203 .client
204 .post_json("/api/upload/presign", &body.to_string())
205 .await;
206 assert!(resp.status.is_success());
207 let data: Value = resp.json();
208 let s3_key = data["s3_key"].as_str().unwrap().to_string();
209
210 // Put a 1KB "video file"
211 let video_data = vec![0u8; 1024];
212 h.storage
213 .as_ref()
214 .unwrap()
215 .put(&s3_key, video_data);
216
217 let body = json!({
218 "item_id": item_id,
219 "file_type": "video",
220 "s3_key": s3_key,
221 });
222 let resp = h
223 .client
224 .post_json("/api/upload/confirm", &body.to_string())
225 .await;
226 assert!(resp.status.is_success(), "Confirm failed: {}", resp.text);
227
228 // Verify video_file_size_bytes is set
229 let size: Option<i64> =
230 sqlx::query_scalar("SELECT video_file_size_bytes FROM items WHERE id = $1::uuid")
231 .bind(&item_id)
232 .fetch_one(&h.db)
233 .await
234 .unwrap();
235 assert!(size.unwrap() > 0, "video_file_size_bytes should be set");
236
237 // Verify storage_used_bytes is updated
238 let storage: i64 = sqlx::query_scalar(
239 "SELECT storage_used_bytes FROM users WHERE id = $1::uuid",
240 )
241 .bind(&user_id)
242 .fetch_one(&h.db)
243 .await
244 .unwrap();
245 assert!(storage > 0, "storage_used_bytes should include video");
246 }
247
248 #[tokio::test]
249 async fn video_wizard_group() {
250 // Verify Video item type gets "video" wizard group (unit-level, but validates the integration)
251 use makenotwork::db::ItemType;
252 assert_eq!(ItemType::Video.wizard_group(), "video");
253 assert_ne!(ItemType::Video.wizard_group(), "file");
254 }
255