this repo has no description
1mod common;
2mod helpers;
3use common::*;
4use helpers::*;
5use reqwest::StatusCode;
6use serde_json::{Value, json};
7use std::time::Duration;
8use chrono::Utc;
9
10#[tokio::test]
11async fn test_social_flow_lifecycle() {
12 let client = client();
13 let (alice_did, alice_jwt) = setup_new_user("alice-social").await;
14 let (bob_did, bob_jwt) = setup_new_user("bob-social").await;
15 let (post1_uri, _) = create_post(&client, &alice_did, &alice_jwt, "Alice's first post!").await;
16 create_follow(&client, &bob_did, &bob_jwt, &alice_did).await;
17 tokio::time::sleep(Duration::from_secs(1)).await;
18 let timeline_res_1 = client
19 .get(format!(
20 "{}/xrpc/app.bsky.feed.getTimeline",
21 base_url().await
22 ))
23 .bearer_auth(&bob_jwt)
24 .send()
25 .await
26 .expect("Failed to get timeline (1)");
27 assert_eq!(
28 timeline_res_1.status(),
29 reqwest::StatusCode::OK,
30 "Failed to get timeline (1)"
31 );
32 let timeline_body_1: Value = timeline_res_1.json().await.expect("Timeline (1) not JSON");
33 let feed_1 = timeline_body_1["feed"].as_array().unwrap();
34 assert_eq!(feed_1.len(), 1, "Timeline should have 1 post");
35 assert_eq!(
36 feed_1[0]["post"]["uri"], post1_uri,
37 "Post URI mismatch in timeline (1)"
38 );
39 let (post2_uri, _) = create_post(
40 &client,
41 &alice_did,
42 &alice_jwt,
43 "Alice's second post, so exciting!",
44 )
45 .await;
46 tokio::time::sleep(Duration::from_secs(1)).await;
47 let timeline_res_2 = client
48 .get(format!(
49 "{}/xrpc/app.bsky.feed.getTimeline",
50 base_url().await
51 ))
52 .bearer_auth(&bob_jwt)
53 .send()
54 .await
55 .expect("Failed to get timeline (2)");
56 assert_eq!(
57 timeline_res_2.status(),
58 reqwest::StatusCode::OK,
59 "Failed to get timeline (2)"
60 );
61 let timeline_body_2: Value = timeline_res_2.json().await.expect("Timeline (2) not JSON");
62 let feed_2 = timeline_body_2["feed"].as_array().unwrap();
63 assert_eq!(feed_2.len(), 2, "Timeline should have 2 posts");
64 assert_eq!(
65 feed_2[0]["post"]["uri"], post2_uri,
66 "Post 2 should be first"
67 );
68 assert_eq!(
69 feed_2[1]["post"]["uri"], post1_uri,
70 "Post 1 should be second"
71 );
72 let delete_payload = json!({
73 "repo": alice_did,
74 "collection": "app.bsky.feed.post",
75 "rkey": post1_uri.split('/').last().unwrap()
76 });
77 let delete_res = client
78 .post(format!(
79 "{}/xrpc/com.atproto.repo.deleteRecord",
80 base_url().await
81 ))
82 .bearer_auth(&alice_jwt)
83 .json(&delete_payload)
84 .send()
85 .await
86 .expect("Failed to send delete request");
87 assert_eq!(
88 delete_res.status(),
89 reqwest::StatusCode::OK,
90 "Failed to delete record"
91 );
92 tokio::time::sleep(Duration::from_secs(1)).await;
93 let timeline_res_3 = client
94 .get(format!(
95 "{}/xrpc/app.bsky.feed.getTimeline",
96 base_url().await
97 ))
98 .bearer_auth(&bob_jwt)
99 .send()
100 .await
101 .expect("Failed to get timeline (3)");
102 assert_eq!(
103 timeline_res_3.status(),
104 reqwest::StatusCode::OK,
105 "Failed to get timeline (3)"
106 );
107 let timeline_body_3: Value = timeline_res_3.json().await.expect("Timeline (3) not JSON");
108 let feed_3 = timeline_body_3["feed"].as_array().unwrap();
109 assert_eq!(feed_3.len(), 1, "Timeline should have 1 post after delete");
110 assert_eq!(
111 feed_3[0]["post"]["uri"], post2_uri,
112 "Only post 2 should remain"
113 );
114}
115
116#[tokio::test]
117async fn test_like_lifecycle() {
118 let client = client();
119 let (alice_did, alice_jwt) = setup_new_user("alice-like").await;
120 let (bob_did, bob_jwt) = setup_new_user("bob-like").await;
121 let (post_uri, post_cid) = create_post(&client, &alice_did, &alice_jwt, "Like this post!").await;
122 let (like_uri, _) = create_like(&client, &bob_did, &bob_jwt, &post_uri, &post_cid).await;
123 let like_rkey = like_uri.split('/').last().unwrap();
124 let get_like_res = client
125 .get(format!(
126 "{}/xrpc/com.atproto.repo.getRecord",
127 base_url().await
128 ))
129 .query(&[
130 ("repo", bob_did.as_str()),
131 ("collection", "app.bsky.feed.like"),
132 ("rkey", like_rkey),
133 ])
134 .send()
135 .await
136 .expect("Failed to get like");
137 assert_eq!(get_like_res.status(), StatusCode::OK);
138 let like_body: Value = get_like_res.json().await.unwrap();
139 assert_eq!(like_body["value"]["subject"]["uri"], post_uri);
140 let delete_payload = json!({
141 "repo": bob_did,
142 "collection": "app.bsky.feed.like",
143 "rkey": like_rkey
144 });
145 let delete_res = client
146 .post(format!(
147 "{}/xrpc/com.atproto.repo.deleteRecord",
148 base_url().await
149 ))
150 .bearer_auth(&bob_jwt)
151 .json(&delete_payload)
152 .send()
153 .await
154 .expect("Failed to delete like");
155 assert_eq!(delete_res.status(), StatusCode::OK, "Failed to delete like");
156 let get_deleted_res = client
157 .get(format!(
158 "{}/xrpc/com.atproto.repo.getRecord",
159 base_url().await
160 ))
161 .query(&[
162 ("repo", bob_did.as_str()),
163 ("collection", "app.bsky.feed.like"),
164 ("rkey", like_rkey),
165 ])
166 .send()
167 .await
168 .expect("Failed to check deleted like");
169 assert_eq!(get_deleted_res.status(), StatusCode::NOT_FOUND, "Like should be deleted");
170}
171
172#[tokio::test]
173async fn test_repost_lifecycle() {
174 let client = client();
175 let (alice_did, alice_jwt) = setup_new_user("alice-repost").await;
176 let (bob_did, bob_jwt) = setup_new_user("bob-repost").await;
177 let (post_uri, post_cid) = create_post(&client, &alice_did, &alice_jwt, "Repost this!").await;
178 let (repost_uri, _) = create_repost(&client, &bob_did, &bob_jwt, &post_uri, &post_cid).await;
179 let repost_rkey = repost_uri.split('/').last().unwrap();
180 let get_repost_res = client
181 .get(format!(
182 "{}/xrpc/com.atproto.repo.getRecord",
183 base_url().await
184 ))
185 .query(&[
186 ("repo", bob_did.as_str()),
187 ("collection", "app.bsky.feed.repost"),
188 ("rkey", repost_rkey),
189 ])
190 .send()
191 .await
192 .expect("Failed to get repost");
193 assert_eq!(get_repost_res.status(), StatusCode::OK);
194 let repost_body: Value = get_repost_res.json().await.unwrap();
195 assert_eq!(repost_body["value"]["subject"]["uri"], post_uri);
196 let delete_payload = json!({
197 "repo": bob_did,
198 "collection": "app.bsky.feed.repost",
199 "rkey": repost_rkey
200 });
201 let delete_res = client
202 .post(format!(
203 "{}/xrpc/com.atproto.repo.deleteRecord",
204 base_url().await
205 ))
206 .bearer_auth(&bob_jwt)
207 .json(&delete_payload)
208 .send()
209 .await
210 .expect("Failed to delete repost");
211 assert_eq!(delete_res.status(), StatusCode::OK, "Failed to delete repost");
212}
213
214#[tokio::test]
215async fn test_unfollow_lifecycle() {
216 let client = client();
217 let (alice_did, _alice_jwt) = setup_new_user("alice-unfollow").await;
218 let (bob_did, bob_jwt) = setup_new_user("bob-unfollow").await;
219 let (follow_uri, _) = create_follow(&client, &bob_did, &bob_jwt, &alice_did).await;
220 let follow_rkey = follow_uri.split('/').last().unwrap();
221 let get_follow_res = client
222 .get(format!(
223 "{}/xrpc/com.atproto.repo.getRecord",
224 base_url().await
225 ))
226 .query(&[
227 ("repo", bob_did.as_str()),
228 ("collection", "app.bsky.graph.follow"),
229 ("rkey", follow_rkey),
230 ])
231 .send()
232 .await
233 .expect("Failed to get follow");
234 assert_eq!(get_follow_res.status(), StatusCode::OK);
235 let unfollow_payload = json!({
236 "repo": bob_did,
237 "collection": "app.bsky.graph.follow",
238 "rkey": follow_rkey
239 });
240 let unfollow_res = client
241 .post(format!(
242 "{}/xrpc/com.atproto.repo.deleteRecord",
243 base_url().await
244 ))
245 .bearer_auth(&bob_jwt)
246 .json(&unfollow_payload)
247 .send()
248 .await
249 .expect("Failed to unfollow");
250 assert_eq!(unfollow_res.status(), StatusCode::OK, "Failed to unfollow");
251 let get_deleted_res = client
252 .get(format!(
253 "{}/xrpc/com.atproto.repo.getRecord",
254 base_url().await
255 ))
256 .query(&[
257 ("repo", bob_did.as_str()),
258 ("collection", "app.bsky.graph.follow"),
259 ("rkey", follow_rkey),
260 ])
261 .send()
262 .await
263 .expect("Failed to check deleted follow");
264 assert_eq!(get_deleted_res.status(), StatusCode::NOT_FOUND, "Follow should be deleted");
265}
266
267#[tokio::test]
268async fn test_timeline_after_unfollow() {
269 let client = client();
270 let (alice_did, alice_jwt) = setup_new_user("alice-tl-unfollow").await;
271 let (bob_did, bob_jwt) = setup_new_user("bob-tl-unfollow").await;
272 let (follow_uri, _) = create_follow(&client, &bob_did, &bob_jwt, &alice_did).await;
273 create_post(&client, &alice_did, &alice_jwt, "Post while following").await;
274 tokio::time::sleep(Duration::from_secs(1)).await;
275 let timeline_res = client
276 .get(format!(
277 "{}/xrpc/app.bsky.feed.getTimeline",
278 base_url().await
279 ))
280 .bearer_auth(&bob_jwt)
281 .send()
282 .await
283 .expect("Failed to get timeline");
284 assert_eq!(timeline_res.status(), StatusCode::OK);
285 let timeline_body: Value = timeline_res.json().await.unwrap();
286 let feed = timeline_body["feed"].as_array().unwrap();
287 assert_eq!(feed.len(), 1, "Should see 1 post from Alice");
288 let follow_rkey = follow_uri.split('/').last().unwrap();
289 let unfollow_payload = json!({
290 "repo": bob_did,
291 "collection": "app.bsky.graph.follow",
292 "rkey": follow_rkey
293 });
294 client
295 .post(format!(
296 "{}/xrpc/com.atproto.repo.deleteRecord",
297 base_url().await
298 ))
299 .bearer_auth(&bob_jwt)
300 .json(&unfollow_payload)
301 .send()
302 .await
303 .expect("Failed to unfollow");
304 tokio::time::sleep(Duration::from_secs(1)).await;
305 let timeline_after_res = client
306 .get(format!(
307 "{}/xrpc/app.bsky.feed.getTimeline",
308 base_url().await
309 ))
310 .bearer_auth(&bob_jwt)
311 .send()
312 .await
313 .expect("Failed to get timeline after unfollow");
314 assert_eq!(timeline_after_res.status(), StatusCode::OK);
315 let timeline_after: Value = timeline_after_res.json().await.unwrap();
316 let feed_after = timeline_after["feed"].as_array().unwrap();
317 assert_eq!(feed_after.len(), 0, "Should see 0 posts after unfollowing");
318}
319
320#[tokio::test]
321async fn test_mutual_follow_lifecycle() {
322 let client = client();
323 let (alice_did, alice_jwt) = setup_new_user("alice-mutual").await;
324 let (bob_did, bob_jwt) = setup_new_user("bob-mutual").await;
325 create_follow(&client, &alice_did, &alice_jwt, &bob_did).await;
326 create_follow(&client, &bob_did, &bob_jwt, &alice_did).await;
327 create_post(&client, &alice_did, &alice_jwt, "Alice's post for mutual").await;
328 create_post(&client, &bob_did, &bob_jwt, "Bob's post for mutual").await;
329 tokio::time::sleep(Duration::from_secs(1)).await;
330 let alice_timeline_res = client
331 .get(format!(
332 "{}/xrpc/app.bsky.feed.getTimeline",
333 base_url().await
334 ))
335 .bearer_auth(&alice_jwt)
336 .send()
337 .await
338 .expect("Failed to get Alice's timeline");
339 assert_eq!(alice_timeline_res.status(), StatusCode::OK);
340 let alice_tl: Value = alice_timeline_res.json().await.unwrap();
341 let alice_feed = alice_tl["feed"].as_array().unwrap();
342 assert_eq!(alice_feed.len(), 1, "Alice should see Bob's 1 post");
343 let bob_timeline_res = client
344 .get(format!(
345 "{}/xrpc/app.bsky.feed.getTimeline",
346 base_url().await
347 ))
348 .bearer_auth(&bob_jwt)
349 .send()
350 .await
351 .expect("Failed to get Bob's timeline");
352 assert_eq!(bob_timeline_res.status(), StatusCode::OK);
353 let bob_tl: Value = bob_timeline_res.json().await.unwrap();
354 let bob_feed = bob_tl["feed"].as_array().unwrap();
355 assert_eq!(bob_feed.len(), 1, "Bob should see Alice's 1 post");
356}
357
358#[tokio::test]
359async fn test_account_to_post_full_lifecycle() {
360 let client = client();
361 let ts = Utc::now().timestamp_millis();
362 let handle = format!("fullcycle-{}.test", ts);
363 let email = format!("fullcycle-{}@test.com", ts);
364 let password = "fullcycle-password";
365 let create_account_res = client
366 .post(format!(
367 "{}/xrpc/com.atproto.server.createAccount",
368 base_url().await
369 ))
370 .json(&json!({
371 "handle": handle,
372 "email": email,
373 "password": password
374 }))
375 .send()
376 .await
377 .expect("Failed to create account");
378 assert_eq!(create_account_res.status(), StatusCode::OK);
379 let account_body: Value = create_account_res.json().await.unwrap();
380 let did = account_body["did"].as_str().unwrap().to_string();
381 let access_jwt = verify_new_account(&client, &did).await;
382 let get_session_res = client
383 .get(format!(
384 "{}/xrpc/com.atproto.server.getSession",
385 base_url().await
386 ))
387 .bearer_auth(&access_jwt)
388 .send()
389 .await
390 .expect("Failed to get session");
391 assert_eq!(get_session_res.status(), StatusCode::OK);
392 let session_body: Value = get_session_res.json().await.unwrap();
393 assert_eq!(session_body["did"], did);
394 assert_eq!(session_body["handle"], handle);
395 let profile_res = client
396 .post(format!(
397 "{}/xrpc/com.atproto.repo.putRecord",
398 base_url().await
399 ))
400 .bearer_auth(&access_jwt)
401 .json(&json!({
402 "repo": did,
403 "collection": "app.bsky.actor.profile",
404 "rkey": "self",
405 "record": {
406 "$type": "app.bsky.actor.profile",
407 "displayName": "Full Cycle User"
408 }
409 }))
410 .send()
411 .await
412 .expect("Failed to create profile");
413 assert_eq!(profile_res.status(), StatusCode::OK);
414 let (post_uri, post_cid) = create_post(&client, &did, &access_jwt, "My first post!").await;
415 let get_post_res = client
416 .get(format!(
417 "{}/xrpc/com.atproto.repo.getRecord",
418 base_url().await
419 ))
420 .query(&[
421 ("repo", did.as_str()),
422 ("collection", "app.bsky.feed.post"),
423 ("rkey", post_uri.split('/').last().unwrap()),
424 ])
425 .send()
426 .await
427 .expect("Failed to get post");
428 assert_eq!(get_post_res.status(), StatusCode::OK);
429 create_like(&client, &did, &access_jwt, &post_uri, &post_cid).await;
430 let describe_res = client
431 .get(format!(
432 "{}/xrpc/com.atproto.repo.describeRepo",
433 base_url().await
434 ))
435 .query(&[("repo", did.as_str())])
436 .send()
437 .await
438 .expect("Failed to describe repo");
439 assert_eq!(describe_res.status(), StatusCode::OK);
440 let describe_body: Value = describe_res.json().await.unwrap();
441 assert_eq!(describe_body["did"], did);
442 assert_eq!(describe_body["handle"], handle);
443}