this repo has no description
1mod common;
2use common::*;
3
4use reqwest::{Client, StatusCode};
5use serde_json::{json, Value};
6use chrono::Utc;
7#[allow(unused_imports)]
8use std::time::Duration;
9
10async fn setup_new_user(handle_prefix: &str) -> (String, String) {
11 let client = client();
12 let ts = Utc::now().timestamp_millis();
13 let handle = format!("{}-{}.test", handle_prefix, ts);
14 let email = format!("{}-{}@test.com", handle_prefix, ts);
15 let password = "e2e-password-123";
16
17 let create_account_payload = json!({
18 "handle": handle,
19 "email": email,
20 "password": password
21 });
22 let create_res = client.post(format!("{}/xrpc/com.atproto.server.createAccount", base_url().await))
23 .json(&create_account_payload)
24 .send()
25 .await
26 .expect("setup_new_user: Failed to send createAccount");
27
28 if create_res.status() != StatusCode::OK {
29 panic!("setup_new_user: Failed to create account: {:?}", create_res.text().await);
30 }
31
32 let create_body: Value = create_res.json().await.expect("setup_new_user: createAccount response was not JSON");
33
34 let new_did = create_body["did"].as_str().expect("setup_new_user: Response had no DID").to_string();
35 let new_jwt = create_body["accessJwt"].as_str().expect("setup_new_user: Response had no accessJwt").to_string();
36
37 (new_did, new_jwt)
38}
39
40#[tokio::test]
41#[ignore]
42async fn test_post_crud_lifecycle() {
43 let client = client();
44 let (did, jwt) = setup_new_user("lifecycle-crud").await;
45 let collection = "app.bsky.feed.post";
46
47 let rkey = format!("e2e_lifecycle_{}", Utc::now().timestamp_millis());
48 let now = Utc::now().to_rfc3339();
49
50 let original_text = "Hello from the lifecycle test!";
51 let create_payload = json!({
52 "repo": did,
53 "collection": collection,
54 "rkey": rkey,
55 "record": {
56 "$type": collection,
57 "text": original_text,
58 "createdAt": now
59 }
60 });
61
62 let create_res = client.post(format!("{}/xrpc/com.atproto.repo.putRecord", base_url().await))
63 .bearer_auth(&jwt)
64 .json(&create_payload)
65 .send()
66 .await
67 .expect("Failed to send create request");
68
69 assert_eq!(create_res.status(), StatusCode::OK, "Failed to create record");
70 let create_body: Value = create_res.json().await.expect("create response was not JSON");
71 let uri = create_body["uri"].as_str().unwrap();
72
73
74 let params = [
75 ("repo", did.as_str()),
76 ("collection", collection),
77 ("rkey", &rkey),
78 ];
79 let get_res = client.get(format!("{}/xrpc/com.atproto.repo.getRecord", base_url().await))
80 .query(¶ms)
81 .send()
82 .await
83 .expect("Failed to send get request");
84
85 assert_eq!(get_res.status(), StatusCode::OK, "Failed to get record after create");
86 let get_body: Value = get_res.json().await.expect("get response was not JSON");
87 assert_eq!(get_body["uri"], uri);
88 assert_eq!(get_body["value"]["text"], original_text);
89
90
91 let updated_text = "This post has been updated.";
92 let update_payload = json!({
93 "repo": did,
94 "collection": collection,
95 "rkey": rkey,
96 "record": {
97 "$type": collection,
98 "text": updated_text,
99 "createdAt": now
100 }
101 });
102
103 let update_res = client.post(format!("{}/xrpc/com.atproto.repo.putRecord", base_url().await))
104 .bearer_auth(&jwt)
105 .json(&update_payload)
106 .send()
107 .await
108 .expect("Failed to send update request");
109
110 assert_eq!(update_res.status(), StatusCode::OK, "Failed to update record");
111
112
113 let get_updated_res = client.get(format!("{}/xrpc/com.atproto.repo.getRecord", base_url().await))
114 .query(¶ms)
115 .send()
116 .await
117 .expect("Failed to send get-after-update request");
118
119 assert_eq!(get_updated_res.status(), StatusCode::OK, "Failed to get record after update");
120 let get_updated_body: Value = get_updated_res.json().await.expect("get-updated response was not JSON");
121 assert_eq!(get_updated_body["value"]["text"], updated_text, "Text was not updated");
122
123
124 let delete_payload = json!({
125 "repo": did,
126 "collection": collection,
127 "rkey": rkey
128 });
129
130 let delete_res = client.post(format!("{}/xrpc/com.atproto.repo.deleteRecord", base_url().await))
131 .bearer_auth(&jwt)
132 .json(&delete_payload)
133 .send()
134 .await
135 .expect("Failed to send delete request");
136
137 assert_eq!(delete_res.status(), StatusCode::OK, "Failed to delete record");
138
139
140 let get_deleted_res = client.get(format!("{}/xrpc/com.atproto.repo.getRecord", base_url().await))
141 .query(¶ms)
142 .send()
143 .await
144 .expect("Failed to send get-after-delete request");
145
146 assert_eq!(get_deleted_res.status(), StatusCode::NOT_FOUND, "Record was found, but it should be deleted");
147}
148
149#[tokio::test]
150#[ignore]
151async fn test_record_update_conflict_lifecycle() {
152 let client = client();
153 let (user_did, user_jwt) = setup_new_user("user-conflict").await;
154
155 let profile_payload = json!({
156 "repo": user_did,
157 "collection": "app.bsky.actor.profile",
158 "rkey": "self",
159 "record": {
160 "$type": "app.bsky.actor.profile",
161 "displayName": "Original Name"
162 }
163 });
164 let create_res = client.post(format!("{}/xrpc/com.atproto.repo.putRecord", base_url().await))
165 .bearer_auth(&user_jwt)
166 .json(&profile_payload)
167 .send().await.expect("create profile failed");
168
169 if create_res.status() != StatusCode::OK {
170 return;
171 }
172
173 let get_res = client.get(format!("{}/xrpc/com.atproto.repo.getRecord", base_url().await))
174 .query(&[
175 ("repo", &user_did),
176 ("collection", &"app.bsky.actor.profile".to_string()),
177 ("rkey", &"self".to_string()),
178 ])
179 .send().await.expect("getRecord failed");
180 let get_body: Value = get_res.json().await.expect("getRecord not json");
181 let cid_v1 = get_body["cid"].as_str().expect("Profile v1 had no CID").to_string();
182
183 let update_payload_v2 = json!({
184 "repo": user_did,
185 "collection": "app.bsky.actor.profile",
186 "rkey": "self",
187 "record": {
188 "$type": "app.bsky.actor.profile",
189 "displayName": "Updated Name (v2)"
190 },
191 "swapCommit": cid_v1 // <-- Correctly point to v1
192 });
193 let update_res_v2 = client.post(format!("{}/xrpc/com.atproto.repo.putRecord", base_url().await))
194 .bearer_auth(&user_jwt)
195 .json(&update_payload_v2)
196 .send().await.expect("putRecord v2 failed");
197 assert_eq!(update_res_v2.status(), StatusCode::OK, "v2 update failed");
198 let update_body_v2: Value = update_res_v2.json().await.expect("v2 body not json");
199 let cid_v2 = update_body_v2["cid"].as_str().expect("v2 response had no CID").to_string();
200
201 let update_payload_v3_stale = json!({
202 "repo": user_did,
203 "collection": "app.bsky.actor.profile",
204 "rkey": "self",
205 "record": {
206 "$type": "app.bsky.actor.profile",
207 "displayName": "Stale Update (v3)"
208 },
209 "swapCommit": cid_v1
210 });
211 let update_res_v3_stale = client.post(format!("{}/xrpc/com.atproto.repo.putRecord", base_url().await))
212 .bearer_auth(&user_jwt)
213 .json(&update_payload_v3_stale)
214 .send().await.expect("putRecord v3 (stale) failed");
215
216 assert_eq!(
217 update_res_v3_stale.status(),
218 StatusCode::CONFLICT,
219 "Stale update did not cause a 409 Conflict"
220 );
221
222 let update_payload_v3_good = json!({
223 "repo": user_did,
224 "collection": "app.bsky.actor.profile",
225 "rkey": "self",
226 "record": {
227 "$type": "app.bsky.actor.profile",
228 "displayName": "Good Update (v3)"
229 },
230 "swapCommit": cid_v2 // <-- Correct
231 });
232 let update_res_v3_good = client.post(format!("{}/xrpc/com.atproto.repo.putRecord", base_url().await))
233 .bearer_auth(&user_jwt)
234 .json(&update_payload_v3_good)
235 .send().await.expect("putRecord v3 (good) failed");
236
237 assert_eq!(update_res_v3_good.status(), StatusCode::OK, "v3 (good) update failed");
238}