I've been saying "PDSes seem easy enough, they're what, some CRUD to a db? I can do that in my sleep". well i'm sleeping rn so let's go
at main 9.6 kB view raw
1mod common; 2use chrono::Utc; 3use common::*; 4use reqwest::StatusCode; 5use serde_json::{Value, json}; 6 7#[tokio::test] 8async fn test_apply_writes_create() { 9 let client = client(); 10 let (token, did) = create_account_and_login(&client).await; 11 let now = Utc::now().to_rfc3339(); 12 let payload = json!({ 13 "repo": did, 14 "writes": [ 15 { 16 "$type": "com.atproto.repo.applyWrites#create", 17 "collection": "app.bsky.feed.post", 18 "value": { 19 "$type": "app.bsky.feed.post", 20 "text": "Batch created post 1", 21 "createdAt": now 22 } 23 }, 24 { 25 "$type": "com.atproto.repo.applyWrites#create", 26 "collection": "app.bsky.feed.post", 27 "value": { 28 "$type": "app.bsky.feed.post", 29 "text": "Batch created post 2", 30 "createdAt": now 31 } 32 } 33 ] 34 }); 35 let res = client 36 .post(format!( 37 "{}/xrpc/com.atproto.repo.applyWrites", 38 base_url().await 39 )) 40 .bearer_auth(&token) 41 .json(&payload) 42 .send() 43 .await 44 .expect("Failed to send request"); 45 assert_eq!(res.status(), StatusCode::OK); 46 let body: Value = res.json().await.expect("Response was not valid JSON"); 47 assert!(body["commit"]["cid"].is_string()); 48 assert!(body["results"].is_array()); 49 let results = body["results"].as_array().unwrap(); 50 assert_eq!(results.len(), 2); 51 assert!(results[0]["uri"].is_string()); 52 assert!(results[0]["cid"].is_string()); 53} 54 55#[tokio::test] 56async fn test_apply_writes_update() { 57 let client = client(); 58 let (token, did) = create_account_and_login(&client).await; 59 let now = Utc::now().to_rfc3339(); 60 let rkey = format!("batch_update_{}", Utc::now().timestamp_millis()); 61 let create_payload = json!({ 62 "repo": did, 63 "collection": "app.bsky.feed.post", 64 "rkey": rkey, 65 "record": { 66 "$type": "app.bsky.feed.post", 67 "text": "Original post", 68 "createdAt": now 69 } 70 }); 71 let res = client 72 .post(format!( 73 "{}/xrpc/com.atproto.repo.putRecord", 74 base_url().await 75 )) 76 .bearer_auth(&token) 77 .json(&create_payload) 78 .send() 79 .await 80 .expect("Failed to create"); 81 assert_eq!(res.status(), StatusCode::OK); 82 let update_payload = json!({ 83 "repo": did, 84 "writes": [ 85 { 86 "$type": "com.atproto.repo.applyWrites#update", 87 "collection": "app.bsky.feed.post", 88 "rkey": rkey, 89 "value": { 90 "$type": "app.bsky.feed.post", 91 "text": "Updated post via applyWrites", 92 "createdAt": now 93 } 94 } 95 ] 96 }); 97 let res = client 98 .post(format!( 99 "{}/xrpc/com.atproto.repo.applyWrites", 100 base_url().await 101 )) 102 .bearer_auth(&token) 103 .json(&update_payload) 104 .send() 105 .await 106 .expect("Failed to send request"); 107 assert_eq!(res.status(), StatusCode::OK); 108 let body: Value = res.json().await.expect("Response was not valid JSON"); 109 let results = body["results"].as_array().unwrap(); 110 assert_eq!(results.len(), 1); 111 assert!(results[0]["uri"].is_string()); 112} 113 114#[tokio::test] 115async fn test_apply_writes_delete() { 116 let client = client(); 117 let (token, did) = create_account_and_login(&client).await; 118 let now = Utc::now().to_rfc3339(); 119 let rkey = format!("batch_delete_{}", Utc::now().timestamp_millis()); 120 let create_payload = json!({ 121 "repo": did, 122 "collection": "app.bsky.feed.post", 123 "rkey": rkey, 124 "record": { 125 "$type": "app.bsky.feed.post", 126 "text": "Post to delete", 127 "createdAt": now 128 } 129 }); 130 let res = client 131 .post(format!( 132 "{}/xrpc/com.atproto.repo.putRecord", 133 base_url().await 134 )) 135 .bearer_auth(&token) 136 .json(&create_payload) 137 .send() 138 .await 139 .expect("Failed to create"); 140 assert_eq!(res.status(), StatusCode::OK); 141 let delete_payload = json!({ 142 "repo": did, 143 "writes": [ 144 { 145 "$type": "com.atproto.repo.applyWrites#delete", 146 "collection": "app.bsky.feed.post", 147 "rkey": rkey 148 } 149 ] 150 }); 151 let res = client 152 .post(format!( 153 "{}/xrpc/com.atproto.repo.applyWrites", 154 base_url().await 155 )) 156 .bearer_auth(&token) 157 .json(&delete_payload) 158 .send() 159 .await 160 .expect("Failed to send request"); 161 assert_eq!(res.status(), StatusCode::OK); 162 let get_res = client 163 .get(format!( 164 "{}/xrpc/com.atproto.repo.getRecord", 165 base_url().await 166 )) 167 .query(&[ 168 ("repo", did.as_str()), 169 ("collection", "app.bsky.feed.post"), 170 ("rkey", rkey.as_str()), 171 ]) 172 .send() 173 .await 174 .expect("Failed to verify"); 175 assert_eq!(get_res.status(), StatusCode::NOT_FOUND); 176} 177 178#[tokio::test] 179async fn test_apply_writes_mixed_operations() { 180 let client = client(); 181 let (token, did) = create_account_and_login(&client).await; 182 let now = Utc::now().to_rfc3339(); 183 let rkey_to_delete = format!("mixed_del_{}", Utc::now().timestamp_millis()); 184 let rkey_to_update = format!("mixed_upd_{}", Utc::now().timestamp_millis()); 185 let setup_payload = json!({ 186 "repo": did, 187 "writes": [ 188 { 189 "$type": "com.atproto.repo.applyWrites#create", 190 "collection": "app.bsky.feed.post", 191 "rkey": rkey_to_delete, 192 "value": { 193 "$type": "app.bsky.feed.post", 194 "text": "To be deleted", 195 "createdAt": now 196 } 197 }, 198 { 199 "$type": "com.atproto.repo.applyWrites#create", 200 "collection": "app.bsky.feed.post", 201 "rkey": rkey_to_update, 202 "value": { 203 "$type": "app.bsky.feed.post", 204 "text": "To be updated", 205 "createdAt": now 206 } 207 } 208 ] 209 }); 210 let res = client 211 .post(format!( 212 "{}/xrpc/com.atproto.repo.applyWrites", 213 base_url().await 214 )) 215 .bearer_auth(&token) 216 .json(&setup_payload) 217 .send() 218 .await 219 .expect("Failed to setup"); 220 assert_eq!(res.status(), StatusCode::OK); 221 let mixed_payload = json!({ 222 "repo": did, 223 "writes": [ 224 { 225 "$type": "com.atproto.repo.applyWrites#create", 226 "collection": "app.bsky.feed.post", 227 "value": { 228 "$type": "app.bsky.feed.post", 229 "text": "New post", 230 "createdAt": now 231 } 232 }, 233 { 234 "$type": "com.atproto.repo.applyWrites#update", 235 "collection": "app.bsky.feed.post", 236 "rkey": rkey_to_update, 237 "value": { 238 "$type": "app.bsky.feed.post", 239 "text": "Updated text", 240 "createdAt": now 241 } 242 }, 243 { 244 "$type": "com.atproto.repo.applyWrites#delete", 245 "collection": "app.bsky.feed.post", 246 "rkey": rkey_to_delete 247 } 248 ] 249 }); 250 let res = client 251 .post(format!( 252 "{}/xrpc/com.atproto.repo.applyWrites", 253 base_url().await 254 )) 255 .bearer_auth(&token) 256 .json(&mixed_payload) 257 .send() 258 .await 259 .expect("Failed to send request"); 260 assert_eq!(res.status(), StatusCode::OK); 261 let body: Value = res.json().await.expect("Response was not valid JSON"); 262 let results = body["results"].as_array().unwrap(); 263 assert_eq!(results.len(), 3); 264} 265 266#[tokio::test] 267async fn test_apply_writes_no_auth() { 268 let client = client(); 269 let payload = json!({ 270 "repo": "did:plc:test", 271 "writes": [ 272 { 273 "$type": "com.atproto.repo.applyWrites#create", 274 "collection": "app.bsky.feed.post", 275 "value": { 276 "$type": "app.bsky.feed.post", 277 "text": "Test", 278 "createdAt": "2025-01-01T00:00:00Z" 279 } 280 } 281 ] 282 }); 283 let res = client 284 .post(format!( 285 "{}/xrpc/com.atproto.repo.applyWrites", 286 base_url().await 287 )) 288 .json(&payload) 289 .send() 290 .await 291 .expect("Failed to send request"); 292 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 293} 294 295#[tokio::test] 296async fn test_apply_writes_empty_writes() { 297 let client = client(); 298 let (token, did) = create_account_and_login(&client).await; 299 let payload = json!({ 300 "repo": did, 301 "writes": [] 302 }); 303 let res = client 304 .post(format!( 305 "{}/xrpc/com.atproto.repo.applyWrites", 306 base_url().await 307 )) 308 .bearer_auth(&token) 309 .json(&payload) 310 .send() 311 .await 312 .expect("Failed to send request"); 313 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 314}