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