this repo has no description
1mod common;
2mod helpers;
3use chrono::Utc;
4use common::*;
5use helpers::*;
6use reqwest::StatusCode;
7use serde_json::{Value, json};
8use std::time::Duration;
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) =
122 create_post(&client, &alice_did, &alice_jwt, "Like this post!").await;
123 let (like_uri, _) = create_like(&client, &bob_did, &bob_jwt, &post_uri, &post_cid).await;
124 let like_rkey = like_uri.split('/').last().unwrap();
125 let get_like_res = client
126 .get(format!(
127 "{}/xrpc/com.atproto.repo.getRecord",
128 base_url().await
129 ))
130 .query(&[
131 ("repo", bob_did.as_str()),
132 ("collection", "app.bsky.feed.like"),
133 ("rkey", like_rkey),
134 ])
135 .send()
136 .await
137 .expect("Failed to get like");
138 assert_eq!(get_like_res.status(), StatusCode::OK);
139 let like_body: Value = get_like_res.json().await.unwrap();
140 assert_eq!(like_body["value"]["subject"]["uri"], post_uri);
141 let delete_payload = json!({
142 "repo": bob_did,
143 "collection": "app.bsky.feed.like",
144 "rkey": like_rkey
145 });
146 let delete_res = client
147 .post(format!(
148 "{}/xrpc/com.atproto.repo.deleteRecord",
149 base_url().await
150 ))
151 .bearer_auth(&bob_jwt)
152 .json(&delete_payload)
153 .send()
154 .await
155 .expect("Failed to delete like");
156 assert_eq!(delete_res.status(), StatusCode::OK, "Failed to delete like");
157 let get_deleted_res = client
158 .get(format!(
159 "{}/xrpc/com.atproto.repo.getRecord",
160 base_url().await
161 ))
162 .query(&[
163 ("repo", bob_did.as_str()),
164 ("collection", "app.bsky.feed.like"),
165 ("rkey", like_rkey),
166 ])
167 .send()
168 .await
169 .expect("Failed to check deleted like");
170 assert_eq!(
171 get_deleted_res.status(),
172 StatusCode::NOT_FOUND,
173 "Like should be deleted"
174 );
175}
176
177#[tokio::test]
178async fn test_repost_lifecycle() {
179 let client = client();
180 let (alice_did, alice_jwt) = setup_new_user("alice-repost").await;
181 let (bob_did, bob_jwt) = setup_new_user("bob-repost").await;
182 let (post_uri, post_cid) = create_post(&client, &alice_did, &alice_jwt, "Repost this!").await;
183 let (repost_uri, _) = create_repost(&client, &bob_did, &bob_jwt, &post_uri, &post_cid).await;
184 let repost_rkey = repost_uri.split('/').last().unwrap();
185 let get_repost_res = client
186 .get(format!(
187 "{}/xrpc/com.atproto.repo.getRecord",
188 base_url().await
189 ))
190 .query(&[
191 ("repo", bob_did.as_str()),
192 ("collection", "app.bsky.feed.repost"),
193 ("rkey", repost_rkey),
194 ])
195 .send()
196 .await
197 .expect("Failed to get repost");
198 assert_eq!(get_repost_res.status(), StatusCode::OK);
199 let repost_body: Value = get_repost_res.json().await.unwrap();
200 assert_eq!(repost_body["value"]["subject"]["uri"], post_uri);
201 let delete_payload = json!({
202 "repo": bob_did,
203 "collection": "app.bsky.feed.repost",
204 "rkey": repost_rkey
205 });
206 let delete_res = client
207 .post(format!(
208 "{}/xrpc/com.atproto.repo.deleteRecord",
209 base_url().await
210 ))
211 .bearer_auth(&bob_jwt)
212 .json(&delete_payload)
213 .send()
214 .await
215 .expect("Failed to delete repost");
216 assert_eq!(
217 delete_res.status(),
218 StatusCode::OK,
219 "Failed to delete repost"
220 );
221}
222
223#[tokio::test]
224async fn test_unfollow_lifecycle() {
225 let client = client();
226 let (alice_did, _alice_jwt) = setup_new_user("alice-unfollow").await;
227 let (bob_did, bob_jwt) = setup_new_user("bob-unfollow").await;
228 let (follow_uri, _) = create_follow(&client, &bob_did, &bob_jwt, &alice_did).await;
229 let follow_rkey = follow_uri.split('/').last().unwrap();
230 let get_follow_res = client
231 .get(format!(
232 "{}/xrpc/com.atproto.repo.getRecord",
233 base_url().await
234 ))
235 .query(&[
236 ("repo", bob_did.as_str()),
237 ("collection", "app.bsky.graph.follow"),
238 ("rkey", follow_rkey),
239 ])
240 .send()
241 .await
242 .expect("Failed to get follow");
243 assert_eq!(get_follow_res.status(), StatusCode::OK);
244 let unfollow_payload = json!({
245 "repo": bob_did,
246 "collection": "app.bsky.graph.follow",
247 "rkey": follow_rkey
248 });
249 let unfollow_res = client
250 .post(format!(
251 "{}/xrpc/com.atproto.repo.deleteRecord",
252 base_url().await
253 ))
254 .bearer_auth(&bob_jwt)
255 .json(&unfollow_payload)
256 .send()
257 .await
258 .expect("Failed to unfollow");
259 assert_eq!(unfollow_res.status(), StatusCode::OK, "Failed to unfollow");
260 let get_deleted_res = client
261 .get(format!(
262 "{}/xrpc/com.atproto.repo.getRecord",
263 base_url().await
264 ))
265 .query(&[
266 ("repo", bob_did.as_str()),
267 ("collection", "app.bsky.graph.follow"),
268 ("rkey", follow_rkey),
269 ])
270 .send()
271 .await
272 .expect("Failed to check deleted follow");
273 assert_eq!(
274 get_deleted_res.status(),
275 StatusCode::NOT_FOUND,
276 "Follow should be deleted"
277 );
278}
279
280#[tokio::test]
281async fn test_timeline_after_unfollow() {
282 let client = client();
283 let (alice_did, alice_jwt) = setup_new_user("alice-tl-unfollow").await;
284 let (bob_did, bob_jwt) = setup_new_user("bob-tl-unfollow").await;
285 let (follow_uri, _) = create_follow(&client, &bob_did, &bob_jwt, &alice_did).await;
286 create_post(&client, &alice_did, &alice_jwt, "Post while following").await;
287 tokio::time::sleep(Duration::from_secs(1)).await;
288 let timeline_res = client
289 .get(format!(
290 "{}/xrpc/app.bsky.feed.getTimeline",
291 base_url().await
292 ))
293 .bearer_auth(&bob_jwt)
294 .send()
295 .await
296 .expect("Failed to get timeline");
297 assert_eq!(timeline_res.status(), StatusCode::OK);
298 let timeline_body: Value = timeline_res.json().await.unwrap();
299 let feed = timeline_body["feed"].as_array().unwrap();
300 assert_eq!(feed.len(), 1, "Should see 1 post from Alice");
301 let follow_rkey = follow_uri.split('/').last().unwrap();
302 let unfollow_payload = json!({
303 "repo": bob_did,
304 "collection": "app.bsky.graph.follow",
305 "rkey": follow_rkey
306 });
307 client
308 .post(format!(
309 "{}/xrpc/com.atproto.repo.deleteRecord",
310 base_url().await
311 ))
312 .bearer_auth(&bob_jwt)
313 .json(&unfollow_payload)
314 .send()
315 .await
316 .expect("Failed to unfollow");
317 tokio::time::sleep(Duration::from_secs(1)).await;
318 let timeline_after_res = client
319 .get(format!(
320 "{}/xrpc/app.bsky.feed.getTimeline",
321 base_url().await
322 ))
323 .bearer_auth(&bob_jwt)
324 .send()
325 .await
326 .expect("Failed to get timeline after unfollow");
327 assert_eq!(timeline_after_res.status(), StatusCode::OK);
328 let timeline_after: Value = timeline_after_res.json().await.unwrap();
329 let feed_after = timeline_after["feed"].as_array().unwrap();
330 assert_eq!(feed_after.len(), 0, "Should see 0 posts after unfollowing");
331}
332
333#[tokio::test]
334async fn test_mutual_follow_lifecycle() {
335 let client = client();
336 let (alice_did, alice_jwt) = setup_new_user("alice-mutual").await;
337 let (bob_did, bob_jwt) = setup_new_user("bob-mutual").await;
338 create_follow(&client, &alice_did, &alice_jwt, &bob_did).await;
339 create_follow(&client, &bob_did, &bob_jwt, &alice_did).await;
340 create_post(&client, &alice_did, &alice_jwt, "Alice's post for mutual").await;
341 create_post(&client, &bob_did, &bob_jwt, "Bob's post for mutual").await;
342 tokio::time::sleep(Duration::from_secs(1)).await;
343 let alice_timeline_res = client
344 .get(format!(
345 "{}/xrpc/app.bsky.feed.getTimeline",
346 base_url().await
347 ))
348 .bearer_auth(&alice_jwt)
349 .send()
350 .await
351 .expect("Failed to get Alice's timeline");
352 assert_eq!(alice_timeline_res.status(), StatusCode::OK);
353 let alice_tl: Value = alice_timeline_res.json().await.unwrap();
354 let alice_feed = alice_tl["feed"].as_array().unwrap();
355 assert_eq!(alice_feed.len(), 1, "Alice should see Bob's 1 post");
356 let bob_timeline_res = client
357 .get(format!(
358 "{}/xrpc/app.bsky.feed.getTimeline",
359 base_url().await
360 ))
361 .bearer_auth(&bob_jwt)
362 .send()
363 .await
364 .expect("Failed to get Bob's timeline");
365 assert_eq!(bob_timeline_res.status(), StatusCode::OK);
366 let bob_tl: Value = bob_timeline_res.json().await.unwrap();
367 let bob_feed = bob_tl["feed"].as_array().unwrap();
368 assert_eq!(bob_feed.len(), 1, "Bob should see Alice's 1 post");
369}
370
371#[tokio::test]
372async fn test_account_to_post_full_lifecycle() {
373 let client = client();
374 let ts = Utc::now().timestamp_millis();
375 let handle = format!("fullcycle-{}.test", ts);
376 let email = format!("fullcycle-{}@test.com", ts);
377 let password = "fullcycle-password";
378 let create_account_res = client
379 .post(format!(
380 "{}/xrpc/com.atproto.server.createAccount",
381 base_url().await
382 ))
383 .json(&json!({
384 "handle": handle,
385 "email": email,
386 "password": password
387 }))
388 .send()
389 .await
390 .expect("Failed to create account");
391 assert_eq!(create_account_res.status(), StatusCode::OK);
392 let account_body: Value = create_account_res.json().await.unwrap();
393 let did = account_body["did"].as_str().unwrap().to_string();
394 let handle = account_body["handle"].as_str().unwrap().to_string();
395 let access_jwt = verify_new_account(&client, &did).await;
396 let get_session_res = client
397 .get(format!(
398 "{}/xrpc/com.atproto.server.getSession",
399 base_url().await
400 ))
401 .bearer_auth(&access_jwt)
402 .send()
403 .await
404 .expect("Failed to get session");
405 assert_eq!(get_session_res.status(), StatusCode::OK);
406 let session_body: Value = get_session_res.json().await.unwrap();
407 assert_eq!(session_body["did"], did);
408 let normalized_handle = session_body["handle"].as_str().unwrap().to_string();
409 assert!(
410 normalized_handle.starts_with(&handle),
411 "Session handle should start with the requested handle"
412 );
413 let profile_res = client
414 .post(format!(
415 "{}/xrpc/com.atproto.repo.putRecord",
416 base_url().await
417 ))
418 .bearer_auth(&access_jwt)
419 .json(&json!({
420 "repo": did,
421 "collection": "app.bsky.actor.profile",
422 "rkey": "self",
423 "record": {
424 "$type": "app.bsky.actor.profile",
425 "displayName": "Full Cycle User"
426 }
427 }))
428 .send()
429 .await
430 .expect("Failed to create profile");
431 assert_eq!(profile_res.status(), StatusCode::OK);
432 let (post_uri, post_cid) = create_post(&client, &did, &access_jwt, "My first post!").await;
433 let get_post_res = client
434 .get(format!(
435 "{}/xrpc/com.atproto.repo.getRecord",
436 base_url().await
437 ))
438 .query(&[
439 ("repo", did.as_str()),
440 ("collection", "app.bsky.feed.post"),
441 ("rkey", post_uri.split('/').last().unwrap()),
442 ])
443 .send()
444 .await
445 .expect("Failed to get post");
446 assert_eq!(get_post_res.status(), StatusCode::OK);
447 create_like(&client, &did, &access_jwt, &post_uri, &post_cid).await;
448 let describe_res = client
449 .get(format!(
450 "{}/xrpc/com.atproto.repo.describeRepo",
451 base_url().await
452 ))
453 .query(&[("repo", did.as_str())])
454 .send()
455 .await
456 .expect("Failed to describe repo");
457 assert_eq!(describe_res.status(), StatusCode::OK);
458 let describe_body: Value = describe_res.json().await.unwrap();
459 assert_eq!(describe_body["did"], did);
460 let describe_handle = describe_body["handle"].as_str().unwrap();
461 assert!(
462 normalized_handle.starts_with(describe_handle) || describe_handle.starts_with(&handle),
463 "describeRepo handle should be related to the requested handle"
464 );
465}