More endpoints

This commit is contained in:
lewis
2025-12-08 17:04:00 +02:00
parent 7ec4861cde
commit a2b2c5b4c9
20 changed files with 2515 additions and 32 deletions
+52
View File
@@ -304,3 +304,55 @@ async fn test_did_web_lifecycle() {
assert_eq!(record_body["value"]["displayName"], "DID Web User");
*/
}
#[tokio::test]
async fn test_get_recommended_did_credentials_success() {
let client = client();
let (access_jwt, _) = create_account_and_login(&client).await;
let res = client
.get(format!(
"{}/xrpc/com.atproto.identity.getRecommendedDidCredentials",
base_url().await
))
.bearer_auth(&access_jwt)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert!(body["rotationKeys"].is_array());
assert!(body["alsoKnownAs"].is_array());
assert!(body["verificationMethods"].is_object());
assert!(body["services"].is_object());
let rotation_keys = body["rotationKeys"].as_array().unwrap();
assert!(!rotation_keys.is_empty());
assert!(rotation_keys[0].as_str().unwrap().starts_with("did:key:"));
let also_known_as = body["alsoKnownAs"].as_array().unwrap();
assert!(!also_known_as.is_empty());
assert!(also_known_as[0].as_str().unwrap().starts_with("at://"));
assert!(body["verificationMethods"]["atproto"].is_string());
assert_eq!(body["services"]["atprotoPds"]["type"], "AtprotoPersonalDataServer");
assert!(body["services"]["atprotoPds"]["endpoint"].is_string());
}
#[tokio::test]
async fn test_get_recommended_did_credentials_no_auth() {
let client = client();
let res = client
.get(format!(
"{}/xrpc/com.atproto.identity.getRecommendedDidCredentials",
base_url().await
))
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert_eq!(body["error"], "AuthenticationRequired");
}
+222
View File
@@ -1620,3 +1620,225 @@ async fn test_account_to_post_full_lifecycle() {
assert_eq!(describe_body["did"], did);
assert_eq!(describe_body["handle"], handle);
}
#[tokio::test]
async fn test_app_password_lifecycle() {
let client = client();
let ts = Utc::now().timestamp_millis();
let handle = format!("apppass-{}.test", ts);
let email = format!("apppass-{}@test.com", ts);
let password = "apppass-password";
let create_res = client
.post(format!(
"{}/xrpc/com.atproto.server.createAccount",
base_url().await
))
.json(&json!({
"handle": handle,
"email": email,
"password": password
}))
.send()
.await
.expect("Failed to create account");
assert_eq!(create_res.status(), StatusCode::OK);
let account: Value = create_res.json().await.unwrap();
let jwt = account["accessJwt"].as_str().unwrap();
let create_app_pass_res = client
.post(format!(
"{}/xrpc/com.atproto.server.createAppPassword",
base_url().await
))
.bearer_auth(jwt)
.json(&json!({ "name": "Test App" }))
.send()
.await
.expect("Failed to create app password");
assert_eq!(create_app_pass_res.status(), StatusCode::OK);
let app_pass: Value = create_app_pass_res.json().await.unwrap();
let app_password = app_pass["password"].as_str().unwrap().to_string();
assert_eq!(app_pass["name"], "Test App");
let list_res = client
.get(format!(
"{}/xrpc/com.atproto.server.listAppPasswords",
base_url().await
))
.bearer_auth(jwt)
.send()
.await
.expect("Failed to list app passwords");
assert_eq!(list_res.status(), StatusCode::OK);
let list_body: Value = list_res.json().await.unwrap();
let passwords = list_body["passwords"].as_array().unwrap();
assert_eq!(passwords.len(), 1);
assert_eq!(passwords[0]["name"], "Test App");
let login_res = client
.post(format!(
"{}/xrpc/com.atproto.server.createSession",
base_url().await
))
.json(&json!({
"identifier": handle,
"password": app_password
}))
.send()
.await
.expect("Failed to login with app password");
assert_eq!(login_res.status(), StatusCode::OK, "App password login should work");
let revoke_res = client
.post(format!(
"{}/xrpc/com.atproto.server.revokeAppPassword",
base_url().await
))
.bearer_auth(jwt)
.json(&json!({ "name": "Test App" }))
.send()
.await
.expect("Failed to revoke app password");
assert_eq!(revoke_res.status(), StatusCode::OK);
let login_after_revoke = client
.post(format!(
"{}/xrpc/com.atproto.server.createSession",
base_url().await
))
.json(&json!({
"identifier": handle,
"password": app_password
}))
.send()
.await
.expect("Failed to attempt login after revoke");
assert!(
login_after_revoke.status() == StatusCode::UNAUTHORIZED
|| login_after_revoke.status() == StatusCode::BAD_REQUEST,
"Revoked app password should not work"
);
let list_after_revoke = client
.get(format!(
"{}/xrpc/com.atproto.server.listAppPasswords",
base_url().await
))
.bearer_auth(jwt)
.send()
.await
.expect("Failed to list after revoke");
let list_after: Value = list_after_revoke.json().await.unwrap();
let passwords_after = list_after["passwords"].as_array().unwrap();
assert_eq!(passwords_after.len(), 0, "No app passwords should remain");
}
#[tokio::test]
async fn test_account_deactivation_lifecycle() {
let client = client();
let ts = Utc::now().timestamp_millis();
let handle = format!("deactivate-{}.test", ts);
let email = format!("deactivate-{}@test.com", ts);
let password = "deactivate-password";
let create_res = client
.post(format!(
"{}/xrpc/com.atproto.server.createAccount",
base_url().await
))
.json(&json!({
"handle": handle,
"email": email,
"password": password
}))
.send()
.await
.expect("Failed to create account");
assert_eq!(create_res.status(), StatusCode::OK);
let account: Value = create_res.json().await.unwrap();
let did = account["did"].as_str().unwrap().to_string();
let jwt = account["accessJwt"].as_str().unwrap().to_string();
let (post_uri, _) = create_post(&client, &did, &jwt, "Post before deactivation").await;
let post_rkey = post_uri.split('/').last().unwrap();
let status_before = client
.get(format!(
"{}/xrpc/com.atproto.server.checkAccountStatus",
base_url().await
))
.bearer_auth(&jwt)
.send()
.await
.expect("Failed to check status");
assert_eq!(status_before.status(), StatusCode::OK);
let status_body: Value = status_before.json().await.unwrap();
assert_eq!(status_body["activated"], true);
let deactivate_res = client
.post(format!(
"{}/xrpc/com.atproto.server.deactivateAccount",
base_url().await
))
.bearer_auth(&jwt)
.json(&json!({}))
.send()
.await
.expect("Failed to deactivate");
assert_eq!(deactivate_res.status(), StatusCode::OK);
let get_post_res = client
.get(format!(
"{}/xrpc/com.atproto.repo.getRecord",
base_url().await
))
.query(&[
("repo", did.as_str()),
("collection", "app.bsky.feed.post"),
("rkey", post_rkey),
])
.send()
.await
.expect("Failed to get post while deactivated");
assert_eq!(get_post_res.status(), StatusCode::OK, "Records should still be readable");
let activate_res = client
.post(format!(
"{}/xrpc/com.atproto.server.activateAccount",
base_url().await
))
.bearer_auth(&jwt)
.json(&json!({}))
.send()
.await
.expect("Failed to reactivate");
assert_eq!(activate_res.status(), StatusCode::OK);
let status_after_activate = client
.get(format!(
"{}/xrpc/com.atproto.server.checkAccountStatus",
base_url().await
))
.bearer_auth(&jwt)
.send()
.await
.expect("Failed to check status after activate");
assert_eq!(status_after_activate.status(), StatusCode::OK);
let (new_post_uri, _) = create_post(&client, &did, &jwt, "Post after reactivation").await;
assert!(!new_post_uri.is_empty(), "Should be able to post after reactivation");
}
+35
View File
@@ -757,3 +757,38 @@ async fn test_apply_writes_empty_writes() {
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn test_list_missing_blobs() {
let client = client();
let (access_jwt, _) = create_account_and_login(&client).await;
let res = client
.get(format!(
"{}/xrpc/com.atproto.repo.listMissingBlobs",
base_url().await
))
.bearer_auth(&access_jwt)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert!(body["blobs"].is_array());
}
#[tokio::test]
async fn test_list_missing_blobs_no_auth() {
let client = client();
let res = client
.get(format!(
"{}/xrpc/com.atproto.repo.listMissingBlobs",
base_url().await
))
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
+93
View File
@@ -317,3 +317,96 @@ async fn test_get_service_auth_missing_aud() {
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn test_check_account_status_success() {
let client = client();
let (access_jwt, _) = create_account_and_login(&client).await;
let res = client
.get(format!(
"{}/xrpc/com.atproto.server.checkAccountStatus",
base_url().await
))
.bearer_auth(&access_jwt)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert_eq!(body["activated"], true);
assert_eq!(body["validDid"], true);
assert!(body["repoCommit"].is_string());
assert!(body["repoRev"].is_string());
assert!(body["indexedRecords"].is_number());
}
#[tokio::test]
async fn test_check_account_status_no_auth() {
let client = client();
let res = client
.get(format!(
"{}/xrpc/com.atproto.server.checkAccountStatus",
base_url().await
))
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert_eq!(body["error"], "AuthenticationRequired");
}
#[tokio::test]
async fn test_activate_account_success() {
let client = client();
let (access_jwt, _) = create_account_and_login(&client).await;
let res = client
.post(format!(
"{}/xrpc/com.atproto.server.activateAccount",
base_url().await
))
.bearer_auth(&access_jwt)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_activate_account_no_auth() {
let client = client();
let res = client
.post(format!(
"{}/xrpc/com.atproto.server.activateAccount",
base_url().await
))
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn test_deactivate_account_success() {
let client = client();
let (access_jwt, _) = create_account_and_login(&client).await;
let res = client
.post(format!(
"{}/xrpc/com.atproto.server.deactivateAccount",
base_url().await
))
.bearer_auth(&access_jwt)
.json(&json!({}))
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
}
+201
View File
@@ -1,6 +1,7 @@
mod common;
use common::*;
use reqwest::StatusCode;
use reqwest::header;
use serde_json::Value;
#[tokio::test]
@@ -151,3 +152,203 @@ async fn test_list_repos_pagination() {
assert_ne!(repos[0]["did"], repos2[0]["did"]);
}
}
#[tokio::test]
async fn test_get_repo_status_success() {
let client = client();
let (_, did) = create_account_and_login(&client).await;
let params = [("did", did.as_str())];
let res = client
.get(format!(
"{}/xrpc/com.atproto.sync.getRepoStatus",
base_url().await
))
.query(&params)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert_eq!(body["did"], did);
assert_eq!(body["active"], true);
assert!(body["rev"].is_string());
}
#[tokio::test]
async fn test_get_repo_status_not_found() {
let client = client();
let params = [("did", "did:plc:nonexistent12345")];
let res = client
.get(format!(
"{}/xrpc/com.atproto.sync.getRepoStatus",
base_url().await
))
.query(&params)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert_eq!(body["error"], "RepoNotFound");
}
#[tokio::test]
async fn test_list_blobs_success() {
let client = client();
let (access_jwt, did) = create_account_and_login(&client).await;
let blob_res = client
.post(format!(
"{}/xrpc/com.atproto.repo.uploadBlob",
base_url().await
))
.header(header::CONTENT_TYPE, "text/plain")
.bearer_auth(&access_jwt)
.body("test blob content")
.send()
.await
.expect("Failed to upload blob");
assert_eq!(blob_res.status(), StatusCode::OK);
let params = [("did", did.as_str())];
let res = client
.get(format!(
"{}/xrpc/com.atproto.sync.listBlobs",
base_url().await
))
.query(&params)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert!(body["cids"].is_array());
let cids = body["cids"].as_array().unwrap();
assert!(!cids.is_empty());
}
#[tokio::test]
async fn test_list_blobs_not_found() {
let client = client();
let params = [("did", "did:plc:nonexistent12345")];
let res = client
.get(format!(
"{}/xrpc/com.atproto.sync.listBlobs",
base_url().await
))
.query(&params)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert_eq!(body["error"], "RepoNotFound");
}
#[tokio::test]
async fn test_get_blob_success() {
let client = client();
let (access_jwt, did) = create_account_and_login(&client).await;
let blob_content = "test blob for get_blob";
let blob_res = client
.post(format!(
"{}/xrpc/com.atproto.repo.uploadBlob",
base_url().await
))
.header(header::CONTENT_TYPE, "text/plain")
.bearer_auth(&access_jwt)
.body(blob_content)
.send()
.await
.expect("Failed to upload blob");
assert_eq!(blob_res.status(), StatusCode::OK);
let blob_body: Value = blob_res.json().await.expect("Response was not valid JSON");
let cid = blob_body["blob"]["ref"]["$link"].as_str().expect("No CID");
let params = [("did", did.as_str()), ("cid", cid)];
let res = client
.get(format!(
"{}/xrpc/com.atproto.sync.getBlob",
base_url().await
))
.query(&params)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers()
.get("content-type")
.and_then(|h| h.to_str().ok()),
Some("text/plain")
);
let body = res.text().await.expect("Failed to get body");
assert_eq!(body, blob_content);
}
#[tokio::test]
async fn test_get_blob_not_found() {
let client = client();
let (_, did) = create_account_and_login(&client).await;
let params = [
("did", did.as_str()),
("cid", "bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku"),
];
let res = client
.get(format!(
"{}/xrpc/com.atproto.sync.getBlob",
base_url().await
))
.query(&params)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let body: Value = res.json().await.expect("Response was not valid JSON");
assert_eq!(body["error"], "BlobNotFound");
}
#[tokio::test]
async fn test_notify_of_update() {
let client = client();
let params = [("hostname", "example.com")];
let res = client
.post(format!(
"{}/xrpc/com.atproto.sync.notifyOfUpdate",
base_url().await
))
.query(&params)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_request_crawl() {
let client = client();
let payload = serde_json::json!({"hostname": "example.com"});
let res = client
.post(format!(
"{}/xrpc/com.atproto.sync.requestCrawl",
base_url().await
))
.json(&payload)
.send()
.await
.expect("Failed to send request");
assert_eq!(res.status(), StatusCode::OK);
}