Skip to main content

max / makenotwork

13.5 KB · 452 lines History Blame Raw
1 //! Content protection integration tests:
2 //! - License verification (phone-home activation binding, JWT)
3 //! - License deactivation (free slot)
4
5 use crate::harness::TestHarness;
6 use serde_json::Value;
7
8 // =============================================================================
9 // License Verification (phone-home)
10 // =============================================================================
11
12 #[tokio::test]
13 async fn license_verify_requires_verification_enabled() {
14 let mut h = TestHarness::new().await;
15
16 let setup = h.create_creator_with_item("verifymaker", "plugin", 0).await;
17
18 // Enable license keys
19 let resp = h
20 .client
21 .put_form(
22 &format!("/api/items/{}/license-settings", setup.item_id),
23 "enable_license_keys=on&default_max_activations=3",
24 )
25 .await;
26 assert!(resp.status.is_success());
27
28 // Generate a key
29 let resp = h
30 .client
31 .post_form(&format!("/api/items/{}/keys", setup.item_id), "")
32 .await;
33 let key: Value = resp.json();
34 let key_code = key["key_code"].as_str().unwrap();
35
36 // Try to verify — should fail because project doesn't have verification enabled
37 let resp = h
38 .client
39 .post_json(
40 "/api/v1/license/verify",
41 &format!(
42 r#"{{"key": "{}", "machine_fingerprint": "machine-aaa"}}"#,
43 key_code
44 ),
45 )
46 .await;
47 assert!(resp.status.is_success());
48 let body: Value = resp.json();
49 assert_eq!(body["valid"], false);
50 assert_eq!(body["error"], "verification_not_enabled");
51 }
52
53 #[tokio::test]
54 async fn license_verify_lifecycle() {
55 let mut h = TestHarness::new().await;
56
57 let setup = h.create_creator_with_item("verifylc", "plugin", 0).await;
58
59 // Enable license keys
60 h.client
61 .put_form(
62 &format!("/api/items/{}/license-settings", setup.item_id),
63 "enable_license_keys=on&default_max_activations=3",
64 )
65 .await;
66
67 // Enable license verification on the project
68 sqlx::query("UPDATE projects SET license_verification_enabled = true WHERE id = $1::uuid")
69 .bind(&setup.project_id)
70 .execute(&h.db)
71 .await
72 .unwrap();
73
74 // Generate a key
75 let resp = h
76 .client
77 .post_form(&format!("/api/items/{}/keys", setup.item_id), "")
78 .await;
79 let key: Value = resp.json();
80 let key_code = key["key_code"].as_str().unwrap();
81
82 // Verify — should succeed and return JWT
83 let resp = h
84 .client
85 .post_json(
86 "/api/v1/license/verify",
87 &format!(
88 r#"{{"key": "{}", "machine_fingerprint": "machine-001"}}"#,
89 key_code
90 ),
91 )
92 .await;
93 assert!(resp.status.is_success(), "Verify failed: {}", resp.text);
94 let body: Value = resp.json();
95 assert_eq!(body["valid"], true, "Key should be valid");
96 assert!(body["token"].is_string(), "Should return a JWT token");
97 assert!(body["expires_in"].as_i64().unwrap() > 0, "Should have expiry");
98
99 // Re-verify same machine — should succeed (idempotent)
100 let resp = h
101 .client
102 .post_json(
103 "/api/v1/license/verify",
104 &format!(
105 r#"{{"key": "{}", "machine_fingerprint": "machine-001"}}"#,
106 key_code
107 ),
108 )
109 .await;
110 let body: Value = resp.json();
111 assert_eq!(body["valid"], true, "Re-verify should succeed");
112
113 // Verify on second machine
114 let resp = h
115 .client
116 .post_json(
117 "/api/v1/license/verify",
118 &format!(
119 r#"{{"key": "{}", "machine_fingerprint": "machine-002"}}"#,
120 key_code
121 ),
122 )
123 .await;
124 let body: Value = resp.json();
125 assert_eq!(body["valid"], true, "Second machine should work");
126
127 // Verify on third machine
128 let resp = h
129 .client
130 .post_json(
131 "/api/v1/license/verify",
132 &format!(
133 r#"{{"key": "{}", "machine_fingerprint": "machine-003"}}"#,
134 key_code
135 ),
136 )
137 .await;
138 let body: Value = resp.json();
139 assert_eq!(body["valid"], true, "Third machine should work (max=3)");
140
141 // Fourth machine — should hit activation cap
142 let resp = h
143 .client
144 .post_json(
145 "/api/v1/license/verify",
146 &format!(
147 r#"{{"key": "{}", "machine_fingerprint": "machine-004"}}"#,
148 key_code
149 ),
150 )
151 .await;
152 let body: Value = resp.json();
153 assert_eq!(body["valid"], false, "Fourth machine should be rejected");
154 assert_eq!(body["error"], "activation_limit_reached");
155 }
156
157 #[tokio::test]
158 async fn license_deactivate_frees_slot() {
159 let mut h = TestHarness::new().await;
160
161 let setup = h.create_creator_with_item("deactlc", "plugin", 0).await;
162
163 // Enable license keys + verification
164 h.client
165 .put_form(
166 &format!("/api/items/{}/license-settings", setup.item_id),
167 "enable_license_keys=on&default_max_activations=2",
168 )
169 .await;
170 sqlx::query("UPDATE projects SET license_verification_enabled = true WHERE id = $1::uuid")
171 .bind(&setup.project_id)
172 .execute(&h.db)
173 .await
174 .unwrap();
175
176 // Generate key
177 let resp = h
178 .client
179 .post_form(&format!("/api/items/{}/keys", setup.item_id), "")
180 .await;
181 let key: Value = resp.json();
182 let key_code = key["key_code"].as_str().unwrap();
183
184 // Activate on 2 machines (max)
185 for m in &["m-1", "m-2"] {
186 let resp = h
187 .client
188 .post_json(
189 "/api/v1/license/verify",
190 &format!(r#"{{"key": "{}", "machine_fingerprint": "{}"}}"#, key_code, m),
191 )
192 .await;
193 let body: Value = resp.json();
194 assert_eq!(body["valid"], true);
195 }
196
197 // Third machine should fail
198 let resp = h
199 .client
200 .post_json(
201 "/api/v1/license/verify",
202 &format!(
203 r#"{{"key": "{}", "machine_fingerprint": "m-3"}}"#,
204 key_code
205 ),
206 )
207 .await;
208 let body: Value = resp.json();
209 assert_eq!(body["valid"], false);
210
211 // Deactivate machine 1
212 let resp = h
213 .client
214 .post_json(
215 "/api/v1/license/deactivate",
216 &format!(
217 r#"{{"key": "{}", "machine_fingerprint": "m-1"}}"#,
218 key_code
219 ),
220 )
221 .await;
222 assert!(resp.status.is_success());
223 let body: Value = resp.json();
224 assert_eq!(body["success"], true);
225
226 // Third machine should now work (slot freed)
227 let resp = h
228 .client
229 .post_json(
230 "/api/v1/license/verify",
231 &format!(
232 r#"{{"key": "{}", "machine_fingerprint": "m-3"}}"#,
233 key_code
234 ),
235 )
236 .await;
237 let body: Value = resp.json();
238 assert_eq!(body["valid"], true, "Should work after deactivation freed a slot");
239 }
240
241 #[tokio::test]
242 async fn license_verify_revoked_key() {
243 let mut h = TestHarness::new().await;
244
245 let setup = h.create_creator_with_item("revokelc", "plugin", 0).await;
246
247 // Enable license keys + verification
248 h.client
249 .put_form(
250 &format!("/api/items/{}/license-settings", setup.item_id),
251 "enable_license_keys=on&default_max_activations=3",
252 )
253 .await;
254 sqlx::query("UPDATE projects SET license_verification_enabled = true WHERE id = $1::uuid")
255 .bind(&setup.project_id)
256 .execute(&h.db)
257 .await
258 .unwrap();
259
260 // Generate key
261 let resp = h
262 .client
263 .post_form(&format!("/api/items/{}/keys", setup.item_id), "")
264 .await;
265 let key: Value = resp.json();
266 let key_code = key["key_code"].as_str().unwrap();
267 let key_id = key["id"].as_str().unwrap();
268
269 // Revoke the key
270 let resp = h
271 .client
272 .post_form(&format!("/api/keys/{}/revoke", key_id), "")
273 .await;
274 assert!(resp.status.is_success() || resp.status == 204);
275
276 // Verify should fail with key_revoked
277 let resp = h
278 .client
279 .post_json(
280 "/api/v1/license/verify",
281 &format!(
282 r#"{{"key": "{}", "machine_fingerprint": "machine-x"}}"#,
283 key_code
284 ),
285 )
286 .await;
287 let body: Value = resp.json();
288 assert_eq!(body["valid"], false);
289 assert_eq!(body["error"], "key_revoked");
290 }
291
292 // =============================================================================
293 // Project license_verification_enabled flag
294 // =============================================================================
295
296 #[tokio::test]
297 async fn project_license_verification_flag() {
298 let mut h = TestHarness::new().await;
299 let _user_id = h.create_creator("flagmaker").await;
300
301 let resp = h
302 .client
303 .post_form("/api/projects", "slug=flagproj&title=FlagProject")
304 .await;
305 let project: Value = resp.json();
306 let project_id = project["id"].as_str().unwrap();
307
308 // Default should be false
309 let enabled: bool = sqlx::query_scalar(
310 "SELECT license_verification_enabled FROM projects WHERE id = $1::uuid",
311 )
312 .bind(project_id)
313 .fetch_one(&h.db)
314 .await
315 .unwrap();
316 assert!(!enabled, "Should default to false");
317
318 // Enable it
319 sqlx::query("UPDATE projects SET license_verification_enabled = true WHERE id = $1::uuid")
320 .bind(project_id)
321 .execute(&h.db)
322 .await
323 .unwrap();
324
325 let enabled: bool = sqlx::query_scalar(
326 "SELECT license_verification_enabled FROM projects WHERE id = $1::uuid",
327 )
328 .bind(project_id)
329 .fetch_one(&h.db)
330 .await
331 .unwrap();
332 assert!(enabled, "Should be true after update");
333 }
334
335 // =============================================================================
336 // License Text
337 // =============================================================================
338
339 #[tokio::test]
340 async fn license_text_set_and_serve_preset() {
341 let mut h = TestHarness::new().await;
342 let setup = h.create_creator_with_item("lictxt", "digital", 0).await;
343
344 // Set license preset via license-settings endpoint
345 let resp = h
346 .client
347 .put_form(
348 &format!("/api/items/{}/license-settings", setup.item_id),
349 "license_preset=mit",
350 )
351 .await;
352 assert!(resp.status.is_success(), "Set license preset failed: {}", resp.text);
353
354 // GET license.txt should return rendered MIT text
355 let resp = h
356 .client
357 .get(&format!("/api/items/{}/license.txt", setup.item_id))
358 .await;
359 assert!(resp.status.is_success(), "GET license.txt failed: {}", resp.text);
360 assert!(resp.text.contains("MIT License"), "Should contain MIT License text");
361 assert!(resp.text.contains("lictxt"), "Should contain owner username");
362 }
363
364 #[tokio::test]
365 async fn license_text_custom_preset() {
366 let mut h = TestHarness::new().await;
367 let setup = h.create_creator_with_item("licust", "digital", 0).await;
368
369 // Set custom license preset
370 let resp = h
371 .client
372 .put_form(
373 &format!("/api/items/{}/license-settings", setup.item_id),
374 "license_preset=custom&custom_license_text=My+custom+license+terms.",
375 )
376 .await;
377 assert!(resp.status.is_success(), "Set custom license failed: {}", resp.text);
378
379 // GET license.txt should return custom text
380 let resp = h
381 .client
382 .get(&format!("/api/items/{}/license.txt", setup.item_id))
383 .await;
384 assert!(resp.status.is_success(), "GET license.txt failed: {}", resp.text);
385 assert_eq!(resp.text.trim(), "My custom license terms.");
386 }
387
388 #[tokio::test]
389 async fn license_text_no_license_returns_404() {
390 let mut h = TestHarness::new().await;
391 let setup = h.create_creator_with_item("licnone", "digital", 0).await;
392
393 // No license set — should return 404
394 let resp = h
395 .client
396 .get(&format!("/api/items/{}/license.txt", setup.item_id))
397 .await;
398 assert_eq!(resp.status, 404, "Should return 404 when no license set");
399 }
400
401 #[tokio::test]
402 async fn license_text_clear_license() {
403 let mut h = TestHarness::new().await;
404 let setup = h.create_creator_with_item("licclr", "digital", 0).await;
405
406 // Set license, then clear it
407 h.client
408 .put_form(
409 &format!("/api/items/{}/license-settings", setup.item_id),
410 "license_preset=cc0",
411 )
412 .await;
413
414 // Verify it was set
415 let resp = h
416 .client
417 .get(&format!("/api/items/{}/license.txt", setup.item_id))
418 .await;
419 assert!(resp.status.is_success());
420
421 // Clear the license (send empty preset)
422 h.client
423 .put_form(
424 &format!("/api/items/{}/license-settings", setup.item_id),
425 "license_preset=",
426 )
427 .await;
428
429 // Should now return 404
430 let resp = h
431 .client
432 .get(&format!("/api/items/{}/license.txt", setup.item_id))
433 .await;
434 assert_eq!(resp.status, 404, "Should return 404 after clearing license");
435 }
436
437 #[tokio::test]
438 async fn license_text_custom_without_text_fails() {
439 let mut h = TestHarness::new().await;
440 let setup = h.create_creator_with_item("licval", "digital", 0).await;
441
442 // Custom preset without text should fail validation
443 let resp = h
444 .client
445 .put_form(
446 &format!("/api/items/{}/license-settings", setup.item_id),
447 "license_preset=custom",
448 )
449 .await;
450 assert!(!resp.status.is_success(), "Should reject custom preset without text");
451 }
452