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