Account lifecycle conf. vs ref

This commit is contained in:
lewis
2025-12-29 21:51:09 +02:00
parent c58255daa1
commit c302d2aea1
39 changed files with 1338 additions and 144 deletions
+279
View File
@@ -0,0 +1,279 @@
mod common;
mod helpers;
use common::*;
use reqwest::StatusCode;
use serde_json::{Value, json};
#[tokio::test]
async fn test_check_account_status_returns_correct_block_count() {
let client = client();
let base = base_url().await;
let (access_jwt, did) = create_account_and_login(&client).await;
let status1 = client
.get(format!("{}/xrpc/com.atproto.server.checkAccountStatus", base))
.bearer_auth(&access_jwt)
.send()
.await
.unwrap();
assert_eq!(status1.status(), StatusCode::OK);
let body1: Value = status1.json().await.unwrap();
let initial_blocks = body1["repoBlocks"].as_i64().unwrap();
assert!(initial_blocks >= 2, "New account should have at least 2 blocks (commit + empty MST)");
let create_res = client
.post(format!("{}/xrpc/com.atproto.repo.createRecord", base))
.bearer_auth(&access_jwt)
.json(&json!({
"repo": did,
"collection": "app.bsky.feed.post",
"record": {
"$type": "app.bsky.feed.post",
"text": "Test post for block counting",
"createdAt": chrono::Utc::now().to_rfc3339()
}
}))
.send()
.await
.unwrap();
assert_eq!(create_res.status(), StatusCode::OK);
let create_body: Value = create_res.json().await.unwrap();
let rkey = create_body["uri"].as_str().unwrap().split('/').last().unwrap().to_string();
let status2 = client
.get(format!("{}/xrpc/com.atproto.server.checkAccountStatus", base))
.bearer_auth(&access_jwt)
.send()
.await
.unwrap();
let body2: Value = status2.json().await.unwrap();
let after_create_blocks = body2["repoBlocks"].as_i64().unwrap();
assert!(after_create_blocks > initial_blocks, "Block count should increase after creating a record");
let delete_res = client
.post(format!("{}/xrpc/com.atproto.repo.deleteRecord", base))
.bearer_auth(&access_jwt)
.json(&json!({
"repo": did,
"collection": "app.bsky.feed.post",
"rkey": rkey
}))
.send()
.await
.unwrap();
assert_eq!(delete_res.status(), StatusCode::OK);
let status3 = client
.get(format!("{}/xrpc/com.atproto.server.checkAccountStatus", base))
.bearer_auth(&access_jwt)
.send()
.await
.unwrap();
let body3: Value = status3.json().await.unwrap();
let after_delete_blocks = body3["repoBlocks"].as_i64().unwrap();
assert!(
after_delete_blocks >= after_create_blocks,
"Block count should not decrease after deleting a record (was {}, now {})",
after_create_blocks,
after_delete_blocks
);
}
#[tokio::test]
async fn test_check_account_status_returns_valid_repo_rev() {
let client = client();
let base = base_url().await;
let (access_jwt, _) = create_account_and_login(&client).await;
let status = client
.get(format!("{}/xrpc/com.atproto.server.checkAccountStatus", base))
.bearer_auth(&access_jwt)
.send()
.await
.unwrap();
assert_eq!(status.status(), StatusCode::OK);
let body: Value = status.json().await.unwrap();
let repo_rev = body["repoRev"].as_str().unwrap();
assert!(!repo_rev.is_empty(), "repoRev should not be empty");
assert!(repo_rev.chars().all(|c| c.is_alphanumeric()), "repoRev should be alphanumeric TID");
}
#[tokio::test]
async fn test_check_account_status_valid_did_is_true_for_active_account() {
let client = client();
let base = base_url().await;
let (access_jwt, _) = create_account_and_login(&client).await;
let status = client
.get(format!("{}/xrpc/com.atproto.server.checkAccountStatus", base))
.bearer_auth(&access_jwt)
.send()
.await
.unwrap();
assert_eq!(status.status(), StatusCode::OK);
let body: Value = status.json().await.unwrap();
assert_eq!(body["validDid"], true, "validDid should be true for active account with correct DID document");
assert_eq!(body["activated"], true, "activated should be true for active account");
}
#[tokio::test]
async fn test_deactivate_account_with_delete_after() {
let client = client();
let base = base_url().await;
let (access_jwt, _) = create_account_and_login(&client).await;
let future_time = chrono::Utc::now() + chrono::Duration::hours(24);
let delete_after = future_time.to_rfc3339();
let deactivate = client
.post(format!("{}/xrpc/com.atproto.server.deactivateAccount", base))
.bearer_auth(&access_jwt)
.json(&json!({
"deleteAfter": delete_after
}))
.send()
.await
.unwrap();
assert_eq!(deactivate.status(), StatusCode::OK);
let status = client
.get(format!("{}/xrpc/com.atproto.server.checkAccountStatus", base))
.bearer_auth(&access_jwt)
.send()
.await
.unwrap();
assert_eq!(status.status(), StatusCode::OK);
let body: Value = status.json().await.unwrap();
assert_eq!(body["activated"], false, "Account should be deactivated");
}
#[tokio::test]
async fn test_create_account_returns_did_doc() {
let client = client();
let base = base_url().await;
let handle = format!("diddoctest-{}", uuid::Uuid::new_v4());
let payload = json!({
"handle": handle,
"email": format!("{}@example.com", handle),
"password": "Testpass123!"
});
let create_res = client
.post(format!("{}/xrpc/com.atproto.server.createAccount", base))
.json(&payload)
.send()
.await
.unwrap();
assert_eq!(create_res.status(), StatusCode::OK);
let body: Value = create_res.json().await.unwrap();
assert!(body["accessJwt"].is_string(), "accessJwt should always be returned");
assert!(body["refreshJwt"].is_string(), "refreshJwt should always be returned");
assert!(body["did"].is_string(), "did should be returned");
if body["didDoc"].is_object() {
let did_doc = &body["didDoc"];
assert!(did_doc["id"].is_string(), "didDoc should have id field");
}
}
#[tokio::test]
async fn test_create_account_always_returns_tokens() {
let client = client();
let base = base_url().await;
let handle = format!("tokentest-{}", uuid::Uuid::new_v4());
let payload = json!({
"handle": handle,
"email": format!("{}@example.com", handle),
"password": "Testpass123!"
});
let create_res = client
.post(format!("{}/xrpc/com.atproto.server.createAccount", base))
.json(&payload)
.send()
.await
.unwrap();
assert_eq!(create_res.status(), StatusCode::OK);
let body: Value = create_res.json().await.unwrap();
let access_jwt = body["accessJwt"].as_str().expect("accessJwt should be present");
let refresh_jwt = body["refreshJwt"].as_str().expect("refreshJwt should be present");
assert!(!access_jwt.is_empty(), "accessJwt should not be empty");
assert!(!refresh_jwt.is_empty(), "refreshJwt should not be empty");
let parts: Vec<&str> = access_jwt.split('.').collect();
assert_eq!(parts.len(), 3, "accessJwt should be a valid JWT with 3 parts");
}
#[tokio::test]
async fn test_describe_server_has_links_and_contact() {
let client = client();
let base = base_url().await;
let describe = client
.get(format!("{}/xrpc/com.atproto.server.describeServer", base))
.send()
.await
.unwrap();
assert_eq!(describe.status(), StatusCode::OK);
let body: Value = describe.json().await.unwrap();
assert!(body.get("links").is_some(), "describeServer should include links object");
assert!(body.get("contact").is_some(), "describeServer should include contact object");
let links = &body["links"];
assert!(links.get("privacyPolicy").is_some() || links["privacyPolicy"].is_null(),
"links should have privacyPolicy field (can be null)");
assert!(links.get("termsOfService").is_some() || links["termsOfService"].is_null(),
"links should have termsOfService field (can be null)");
let contact = &body["contact"];
assert!(contact.get("email").is_some() || contact["email"].is_null(),
"contact should have email field (can be null)");
}
#[tokio::test]
async fn test_delete_account_password_max_length() {
let client = client();
let base = base_url().await;
let handle = format!("pwdlentest-{}", uuid::Uuid::new_v4());
let payload = json!({
"handle": handle,
"email": format!("{}@example.com", handle),
"password": "Testpass123!"
});
let create_res = client
.post(format!("{}/xrpc/com.atproto.server.createAccount", base))
.json(&payload)
.send()
.await
.unwrap();
assert_eq!(create_res.status(), StatusCode::OK);
let body: Value = create_res.json().await.unwrap();
let did = body["did"].as_str().unwrap();
let too_long_password = "a".repeat(600);
let delete_res = client
.post(format!("{}/xrpc/com.atproto.server.deleteAccount", base))
.json(&json!({
"did": did,
"password": too_long_password,
"token": "fake-token"
}))
.send()
.await
.unwrap();
assert_eq!(delete_res.status(), StatusCode::BAD_REQUEST);
let error_body: Value = delete_res.json().await.unwrap();
assert!(error_body["message"].as_str().unwrap().contains("password length")
|| error_body["error"].as_str().unwrap() == "InvalidRequest");
}
+4 -1
View File
@@ -466,8 +466,11 @@ async fn create_account_and_login_internal(client: &Client, make_admin: bool) ->
.await
.expect("Failed to mark user as admin");
}
let verification_required = body["verificationRequired"].as_bool().unwrap_or(true);
if let Some(access_jwt) = body["accessJwt"].as_str() {
return (access_jwt.to_string(), did);
if !verification_required {
return (access_jwt.to_string(), did);
}
}
let body_text: String = sqlx::query_scalar!(
"SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_verification' ORDER BY created_at DESC LIMIT 1",
+2 -1
View File
@@ -8,6 +8,7 @@ use serde_json::Value;
async fn test_list_blobs_success() {
let client = client();
let (access_jwt, did) = create_account_and_login(&client).await;
let unique_content = format!("test blob content {}", uuid::Uuid::new_v4());
let blob_res = client
.post(format!(
"{}/xrpc/com.atproto.repo.uploadBlob",
@@ -15,7 +16,7 @@ async fn test_list_blobs_success() {
))
.header(header::CONTENT_TYPE, "text/plain")
.bearer_auth(&access_jwt)
.body("test blob content")
.body(unique_content)
.send()
.await
.expect("Failed to upload blob");
+1 -1
View File
@@ -162,7 +162,7 @@ async fn test_list_repos_shows_status_field() {
let res = client
.get(format!(
"{}/xrpc/com.atproto.sync.listRepos",
"{}/xrpc/com.atproto.sync.listRepos?limit=1000",
base_url().await
))
.send()
+4 -3
View File
@@ -552,7 +552,8 @@ async fn test_sync_repo_export_lifecycle() {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
create_post(&client, &did, &jwt, &format!("Export test post {}", i)).await;
}
let blob_data = b"blob data for sync export test";
let blob_data = format!("blob data for sync export test {}", uuid::Uuid::new_v4());
let blob_bytes = blob_data.as_bytes().to_vec();
let upload_res = client
.post(format!(
"{}/xrpc/com.atproto.repo.uploadBlob",
@@ -560,7 +561,7 @@ async fn test_sync_repo_export_lifecycle() {
))
.header(header::CONTENT_TYPE, "application/octet-stream")
.bearer_auth(&jwt)
.body(blob_data.to_vec())
.body(blob_bytes.clone())
.send()
.await
.expect("Failed to upload blob");
@@ -631,7 +632,7 @@ async fn test_sync_repo_export_lifecycle() {
let retrieved_blob = get_blob_res.bytes().await.unwrap();
assert_eq!(
retrieved_blob.as_ref(),
blob_data,
blob_bytes.as_slice(),
"Retrieved blob should match uploaded data"
);
let latest_commit_res = client