Skip to main content

max / makenotwork

5.4 KB · 139 lines History Blame Raw
1 //! Promo code (free access type) generate, list, delete, and claim workflow tests.
2
3 use crate::harness::TestHarness;
4 use serde_json::Value;
5
6 /// Creator setup: signup, grant, re-login, create project + item, publish both.
7 /// Returns (user_id, item_id).
8 async fn setup_creator_with_item(h: &mut TestHarness) -> (makenotwork::db::UserId, String) {
9 let setup = h.create_creator_with_item("dlseller", "digital", 0).await;
10 h.publish_project_and_item(&setup.project_id, &setup.item_id).await;
11 (setup.user_id, setup.item_id)
12 }
13
14 #[tokio::test]
15 async fn free_access_code_generate_list_delete() {
16 let mut h = TestHarness::new().await;
17 let (_user_id, item_id) = setup_creator_with_item(&mut h).await;
18
19 // Generate a free access promo code with max_uses (code auto-generated)
20 let resp = h.client.post_form(
21 "/api/promo-codes",
22 &format!("code_purpose=free_access&item_id={}&max_uses=5", item_id),
23 ).await;
24 assert!(resp.status.is_success(), "Generate free access code failed: {} {}", resp.status, resp.text);
25 let code: Value = resp.json();
26 let code_id = code["id"].as_str().expect("promo code should have id");
27 let key_code = code["code"].as_str().expect("promo code should have code");
28
29 // Verify key code format: 5 or 6 lowercase words separated by dashes.
30 // Generator emits 6 today (raised from 5 after a birthday-collision
31 // review in crypto.rs); validator accepts both for backward compat.
32 let parts: Vec<&str> = key_code.split('-').collect();
33 assert!(
34 matches!(parts.len(), 5 | 6),
35 "KeyCode should have 5 or 6 parts, got {}: {}",
36 parts.len(), key_code
37 );
38 for part in &parts {
39 assert!(!part.is_empty(), "KeyCode parts should be non-empty");
40 assert!(part.chars().all(|c| c.is_ascii_lowercase()), "KeyCode parts should be lowercase: {}", part);
41 }
42
43 // List codes
44 let resp = h.client.get("/api/promo-codes").await;
45 assert!(resp.status.is_success(), "List promo codes failed: {} {}", resp.status, resp.text);
46 let list: Value = resp.json();
47 let data = list["data"].as_array().expect("data should be array");
48 assert!(!data.is_empty());
49
50 // Delete code
51 let resp = h.client.delete(&format!("/api/promo-codes/{}", code_id)).await;
52 assert_eq!(resp.status, 204, "Delete should return 204");
53 }
54
55 #[tokio::test]
56 async fn free_access_code_claim_by_buyer() {
57 let mut h = TestHarness::new().await;
58 let (_user_id, item_id) = setup_creator_with_item(&mut h).await;
59
60 // Generate a free access code
61 let resp = h.client.post_form(
62 "/api/promo-codes",
63 &format!("code_purpose=free_access&item_id={}", item_id),
64 ).await;
65 assert!(resp.status.is_success(), "Generate code failed: {}", resp.text);
66 let code: Value = resp.json();
67 let key_code = code["code"].as_str().unwrap();
68
69 // Switch to buyer
70 h.client.post_form("/logout", "").await;
71 let _buyer_id = h.signup("dlbuyer", "dlbuyer@test.com", "password456").await;
72
73 // Claim the promo code
74 let resp = h.client.post_form(
75 "/api/promo-codes/claim",
76 &format!("code={}", key_code),
77 ).await;
78 assert!(resp.status.is_success(), "Claim failed: {} {}", resp.status, resp.text);
79 let claim: Value = resp.json();
80 assert!(claim["success"].as_bool().unwrap());
81 assert!(!claim["already_owned"].as_bool().unwrap());
82 assert_eq!(claim["item_id"].as_str().unwrap(), item_id);
83
84 // Verify item appears in library
85 let resp = h.client.get("/library").await;
86 assert_eq!(resp.status, 200);
87 assert!(resp.text.contains("Test Item"), "Library should contain the claimed item");
88 }
89
90 #[tokio::test]
91 async fn free_access_code_claim_already_owned() {
92 let mut h = TestHarness::new().await;
93 let (_user_id, item_id) = setup_creator_with_item(&mut h).await;
94
95 // Generate a free access code (unlimited uses)
96 let resp = h.client.post_form(
97 "/api/promo-codes",
98 &format!("code_purpose=free_access&item_id={}", item_id),
99 ).await;
100 let code: Value = resp.json();
101 let key_code = code["code"].as_str().unwrap();
102
103 // Switch to buyer
104 h.client.post_form("/logout", "").await;
105 let _buyer_id = h.signup("dlbuyer2", "dlbuyer2@test.com", "password456").await;
106
107 // First claim
108 let resp = h.client.post_form(
109 "/api/promo-codes/claim",
110 &format!("code={}", key_code),
111 ).await;
112 assert!(resp.status.is_success());
113 let claim: Value = resp.json();
114 assert!(!claim["already_owned"].as_bool().unwrap());
115
116 // Second claim of same code
117 let resp = h.client.post_form(
118 "/api/promo-codes/claim",
119 &format!("code={}", key_code),
120 ).await;
121 assert!(resp.status.is_success());
122 let claim: Value = resp.json();
123 assert!(claim["already_owned"].as_bool().unwrap(), "Second claim should report already_owned");
124 }
125
126 #[tokio::test]
127 async fn free_access_code_claim_invalid_code() {
128 let mut h = TestHarness::new().await;
129 let _buyer_id = h.signup("dlbuyer3", "dlbuyer3@test.com", "password456").await;
130
131 // Try to claim a nonexistent but valid-format code
132 let resp = h.client.post_form(
133 "/api/promo-codes/claim",
134 "code=bright-castle-forest-river-falcon",
135 ).await;
136 assert_eq!(resp.status, 400, "Invalid code should return 400, got {}: {}", resp.status, resp.text);
137 assert!(resp.text.contains("Invalid") || resp.text.contains("invalid"), "Error should mention invalid code");
138 }
139