mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-09-20 17:24:15 +00:00
More endpoints
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(¶ms)
|
||||
.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(¶ms)
|
||||
.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(¶ms)
|
||||
.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(¶ms)
|
||||
.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(¶ms)
|
||||
.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(¶ms)
|
||||
.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(¶ms)
|
||||
.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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user