Skip to main content

max / makenotwork

5.8 KB · 216 lines History Blame Raw
1 //! Follow/unfollow: user follows, project follows, self-follow rejection, nonexistent target.
2
3 use crate::harness::TestHarness;
4 use serde_json::Value;
5
6 #[tokio::test]
7 async fn follow_unfollow_user() {
8 let mut h = TestHarness::new().await;
9
10 // Create two users
11 let user_a = h.signup("follower", "follower@test.com", "password123").await;
12 h.client.post_form("/logout", "").await;
13 let user_b = h.signup("followee", "followee@test.com", "password123").await;
14 h.client.post_form("/logout", "").await;
15
16 // Login as user A
17 h.login("follower", "password123").await;
18
19 // Follow user B
20 let resp = h
21 .client
22 .post_form(&format!("/api/follow/user/{}", *user_b), "")
23 .await;
24 assert!(
25 resp.status.is_success(),
26 "Follow user failed: {} {}",
27 resp.status,
28 resp.text
29 );
30
31 // Response is HTML (FollowButtonTemplate) — check it contains follow state
32 assert!(
33 resp.text.contains("1") || resp.text.contains("follower_count"),
34 "Follow response should contain follower count"
35 );
36
37 // Verify in DB
38 let is_following: bool = sqlx::query_scalar(
39 "SELECT EXISTS(SELECT 1 FROM follows WHERE follower_id = $1 AND target_id = $2)",
40 )
41 .bind(user_a)
42 .bind(*user_b)
43 .fetch_one(&h.db)
44 .await
45 .unwrap();
46 assert!(is_following, "Should be following in DB");
47
48 // Unfollow user B
49 let resp = h
50 .client
51 .delete(&format!("/api/follow/user/{}", *user_b))
52 .await;
53 assert!(
54 resp.status.is_success(),
55 "Unfollow user failed: {} {}",
56 resp.status,
57 resp.text
58 );
59
60 // Verify unfollowed in DB
61 let is_following: bool = sqlx::query_scalar(
62 "SELECT EXISTS(SELECT 1 FROM follows WHERE follower_id = $1 AND target_id = $2)",
63 )
64 .bind(user_a)
65 .bind(*user_b)
66 .fetch_one(&h.db)
67 .await
68 .unwrap();
69 assert!(!is_following, "Should not be following after unfollow");
70 }
71
72 #[tokio::test]
73 async fn follow_unfollow_project() {
74 let mut h = TestHarness::new().await;
75
76 // Create creator with a public project
77 let creator_id = h
78 .signup("projcreator", "projcreator@test.com", "password123")
79 .await;
80 h.grant_creator(creator_id).await;
81 h.client.post_form("/logout", "").await;
82 h.login("projcreator", "password123").await;
83
84 let resp = h
85 .client
86 .post_form("/api/projects", "slug=followproj&title=Follow+Project")
87 .await;
88 assert!(
89 resp.status.is_success(),
90 "Create project failed: {} {}",
91 resp.status,
92 resp.text
93 );
94 let project: Value = resp.json();
95 let project_id = project["id"].as_str().unwrap().to_string();
96
97 // Make it public
98 let resp = h
99 .client
100 .put_json(
101 &format!("/api/projects/{}", project_id),
102 r#"{"visibility": "public"}"#,
103 )
104 .await;
105 assert!(
106 resp.status.is_success(),
107 "Make project public failed: {} {}",
108 resp.status,
109 resp.text
110 );
111
112 // Logout and create a different user to follow the project
113 h.client.post_form("/logout", "").await;
114 let follower_id = h
115 .signup("projfollower", "projfollower@test.com", "password123")
116 .await;
117
118 // Follow the project
119 let resp = h
120 .client
121 .post_form(&format!("/api/follow/project/{}", project_id), "")
122 .await;
123 assert!(
124 resp.status.is_success(),
125 "Follow project failed: {} {}",
126 resp.status,
127 resp.text
128 );
129
130 // Verify in DB
131 let count: i64 = sqlx::query_scalar(
132 "SELECT COUNT(*) FROM follows WHERE follower_id = $1 AND target_id = $2::uuid",
133 )
134 .bind(follower_id)
135 .bind(&project_id)
136 .fetch_one(&h.db)
137 .await
138 .unwrap();
139 assert_eq!(count, 1, "Should have one follow record");
140
141 // Unfollow
142 let resp = h
143 .client
144 .delete(&format!("/api/follow/project/{}", project_id))
145 .await;
146 assert!(
147 resp.status.is_success(),
148 "Unfollow project failed: {} {}",
149 resp.status,
150 resp.text
151 );
152
153 let count: i64 = sqlx::query_scalar(
154 "SELECT COUNT(*) FROM follows WHERE follower_id = $1 AND target_id = $2::uuid",
155 )
156 .bind(follower_id)
157 .bind(&project_id)
158 .fetch_one(&h.db)
159 .await
160 .unwrap();
161 assert_eq!(count, 0, "Follow record should be deleted");
162 }
163
164 #[tokio::test]
165 async fn follow_self_rejected() {
166 let mut h = TestHarness::new().await;
167 let user_id = h
168 .signup("selffollow", "selffollow@test.com", "password123")
169 .await;
170
171 // Try to follow yourself
172 let resp = h
173 .client
174 .post_form(&format!("/api/follow/user/{}", *user_id), "")
175 .await;
176 assert_eq!(
177 resp.status, 400,
178 "Self-follow should be rejected, got {} {}",
179 resp.status, resp.text
180 );
181 }
182
183 #[tokio::test]
184 async fn own_profile_hides_follow_button() {
185 let mut h = TestHarness::new().await;
186 h.signup("selfview", "selfview@test.com", "password123").await;
187
188 let resp = h.client.get("/u/selfview").await;
189 assert_eq!(resp.status, 200);
190 // Should NOT show a follow/unfollow button
191 assert!(
192 !resp.text.contains("follow-btn"),
193 "Own profile should not show follow button"
194 );
195 }
196
197 #[tokio::test]
198 async fn follow_nonexistent_rejected() {
199 let mut h = TestHarness::new().await;
200 let _user_id = h
201 .signup("ghostfollow", "ghostfollow@test.com", "password123")
202 .await;
203
204 // Try to follow a random UUID that doesn't exist
205 let fake_id = uuid::Uuid::new_v4();
206 let resp = h
207 .client
208 .post_form(&format!("/api/follow/user/{}", fake_id), "")
209 .await;
210 assert_eq!(
211 resp.status, 404,
212 "Following nonexistent user should 404, got {} {}",
213 resp.status, resp.text
214 );
215 }
216