mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-09-26 12:14:15 +00:00
More endpoints baybee
This commit is contained in:
+7
-3
@@ -184,12 +184,16 @@ async fn spawn_app(database_url: String) -> String {
|
||||
.await
|
||||
.expect("Failed to run migrations");
|
||||
|
||||
let state = AppState::new(pool).await;
|
||||
let app = bspds::app(state);
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
unsafe {
|
||||
std::env::set_var("PDS_HOSTNAME", addr.to_string());
|
||||
}
|
||||
|
||||
let state = AppState::new(pool).await;
|
||||
let app = bspds::app(state);
|
||||
|
||||
tokio::spawn(async move {
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
});
|
||||
|
||||
+25
-2
@@ -106,7 +106,7 @@ async fn test_create_did_web_account_and_resolve() {
|
||||
|
||||
let handle = format!("webuser_{}", uuid::Uuid::new_v4());
|
||||
|
||||
let pds_endpoint = "https://localhost";
|
||||
let pds_endpoint = base_url().await.replace("http://", "https://");
|
||||
|
||||
let did_doc = json!({
|
||||
"@context": ["https://www.w3.org/ns/did/v1"],
|
||||
@@ -211,10 +211,33 @@ async fn test_create_account_duplicate_handle() {
|
||||
#[tokio::test]
|
||||
async fn test_did_web_lifecycle() {
|
||||
let client = client();
|
||||
|
||||
let mock_server = MockServer::start().await;
|
||||
let mock_uri = mock_server.uri();
|
||||
let mock_addr = mock_uri.trim_start_matches("http://");
|
||||
|
||||
let handle = format!("lifecycle_{}", uuid::Uuid::new_v4());
|
||||
let did = format!("did:web:localhost:u:{}", handle);
|
||||
let did = format!("did:web:{}:u:{}", mock_addr.replace(":", "%3A"), handle);
|
||||
let email = format!("{}@test.com", handle);
|
||||
|
||||
let pds_endpoint = base_url().await.replace("http://", "https://");
|
||||
|
||||
let did_doc = json!({
|
||||
"@context": ["https://www.w3.org/ns/did/v1"],
|
||||
"id": did,
|
||||
"service": [{
|
||||
"id": "#atproto_pds",
|
||||
"type": "AtprotoPersonalDataServer",
|
||||
"serviceEndpoint": pds_endpoint
|
||||
}]
|
||||
});
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!("/u/{}/did.json", handle)))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(did_doc))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let create_payload = json!({
|
||||
"handle": handle,
|
||||
"email": email,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
use base64::Engine;
|
||||
use chrono::Utc;
|
||||
use reqwest::{self, StatusCode, header};
|
||||
use serde_json::{Value, json};
|
||||
@@ -1842,3 +1843,572 @@ async fn test_account_deactivation_lifecycle() {
|
||||
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");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sync_record_lifecycle() {
|
||||
let client = client();
|
||||
let (did, jwt) = setup_new_user("sync-record-lifecycle").await;
|
||||
|
||||
let (post_uri, _post_cid) =
|
||||
create_post(&client, &did, &jwt, "Post for sync record test").await;
|
||||
let post_rkey = post_uri.split('/').last().unwrap();
|
||||
|
||||
let sync_record_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getRecord",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[
|
||||
("did", did.as_str()),
|
||||
("collection", "app.bsky.feed.post"),
|
||||
("rkey", post_rkey),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get sync record");
|
||||
|
||||
assert_eq!(sync_record_res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
sync_record_res
|
||||
.headers()
|
||||
.get("content-type")
|
||||
.and_then(|h| h.to_str().ok()),
|
||||
Some("application/vnd.ipld.car")
|
||||
);
|
||||
let car_bytes = sync_record_res.bytes().await.unwrap();
|
||||
assert!(!car_bytes.is_empty(), "CAR data should not be empty");
|
||||
|
||||
let latest_before = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getLatestCommit",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get latest commit");
|
||||
let latest_before_body: Value = latest_before.json().await.unwrap();
|
||||
let rev_before = latest_before_body["rev"].as_str().unwrap().to_string();
|
||||
|
||||
let (post2_uri, _) = create_post(&client, &did, &jwt, "Second post for sync test").await;
|
||||
|
||||
let latest_after = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getLatestCommit",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get latest commit after");
|
||||
let latest_after_body: Value = latest_after.json().await.unwrap();
|
||||
let rev_after = latest_after_body["rev"].as_str().unwrap().to_string();
|
||||
assert_ne!(rev_before, rev_after, "Revision should change after new record");
|
||||
|
||||
let delete_payload = json!({
|
||||
"repo": did,
|
||||
"collection": "app.bsky.feed.post",
|
||||
"rkey": post_rkey
|
||||
});
|
||||
let delete_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.repo.deleteRecord",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&jwt)
|
||||
.json(&delete_payload)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to delete record");
|
||||
assert_eq!(delete_res.status(), StatusCode::OK);
|
||||
|
||||
let sync_deleted_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getRecord",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[
|
||||
("did", did.as_str()),
|
||||
("collection", "app.bsky.feed.post"),
|
||||
("rkey", post_rkey),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to check deleted record via sync");
|
||||
assert_eq!(
|
||||
sync_deleted_res.status(),
|
||||
StatusCode::NOT_FOUND,
|
||||
"Deleted record should return 404 via sync.getRecord"
|
||||
);
|
||||
|
||||
let post2_rkey = post2_uri.split('/').last().unwrap();
|
||||
let sync_post2_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getRecord",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[
|
||||
("did", did.as_str()),
|
||||
("collection", "app.bsky.feed.post"),
|
||||
("rkey", post2_rkey),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get second post via sync");
|
||||
assert_eq!(
|
||||
sync_post2_res.status(),
|
||||
StatusCode::OK,
|
||||
"Second post should still be accessible"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sync_repo_export_lifecycle() {
|
||||
let client = client();
|
||||
let (did, jwt) = setup_new_user("sync-repo-export").await;
|
||||
|
||||
let profile_payload = json!({
|
||||
"repo": did,
|
||||
"collection": "app.bsky.actor.profile",
|
||||
"rkey": "self",
|
||||
"record": {
|
||||
"$type": "app.bsky.actor.profile",
|
||||
"displayName": "Sync Export User"
|
||||
}
|
||||
});
|
||||
let profile_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.repo.putRecord",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&jwt)
|
||||
.json(&profile_payload)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to create profile");
|
||||
assert_eq!(profile_res.status(), StatusCode::OK);
|
||||
|
||||
for i in 0..3 {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
create_post(&client, &did, &jwt, &format!("Export test post {}", i)).await;
|
||||
}
|
||||
|
||||
let blob_data = b"blob data for sync export test";
|
||||
let upload_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.repo.uploadBlob",
|
||||
base_url().await
|
||||
))
|
||||
.header(header::CONTENT_TYPE, "application/octet-stream")
|
||||
.bearer_auth(&jwt)
|
||||
.body(blob_data.to_vec())
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to upload blob");
|
||||
assert_eq!(upload_res.status(), StatusCode::OK);
|
||||
let blob_body: Value = upload_res.json().await.unwrap();
|
||||
let blob_cid = blob_body["blob"]["ref"]["$link"].as_str().unwrap().to_string();
|
||||
|
||||
let repo_status_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getRepoStatus",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get repo status");
|
||||
assert_eq!(repo_status_res.status(), StatusCode::OK);
|
||||
let status_body: Value = repo_status_res.json().await.unwrap();
|
||||
assert_eq!(status_body["did"], did);
|
||||
assert_eq!(status_body["active"], true);
|
||||
|
||||
let get_repo_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getRepo",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get full repo");
|
||||
assert_eq!(get_repo_res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
get_repo_res
|
||||
.headers()
|
||||
.get("content-type")
|
||||
.and_then(|h| h.to_str().ok()),
|
||||
Some("application/vnd.ipld.car")
|
||||
);
|
||||
let repo_car = get_repo_res.bytes().await.unwrap();
|
||||
assert!(repo_car.len() > 100, "Repo CAR should have substantial data");
|
||||
|
||||
let list_blobs_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.listBlobs",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to list blobs");
|
||||
assert_eq!(list_blobs_res.status(), StatusCode::OK);
|
||||
let blobs_body: Value = list_blobs_res.json().await.unwrap();
|
||||
let cids = blobs_body["cids"].as_array().unwrap();
|
||||
assert!(!cids.is_empty(), "Should have at least one blob");
|
||||
|
||||
let get_blob_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getBlob",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[("did", did.as_str()), ("cid", &blob_cid)])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get blob");
|
||||
assert_eq!(get_blob_res.status(), StatusCode::OK);
|
||||
let retrieved_blob = get_blob_res.bytes().await.unwrap();
|
||||
assert_eq!(
|
||||
retrieved_blob.as_ref(),
|
||||
blob_data,
|
||||
"Retrieved blob should match uploaded data"
|
||||
);
|
||||
|
||||
let latest_commit_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getLatestCommit",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get latest commit");
|
||||
assert_eq!(latest_commit_res.status(), StatusCode::OK);
|
||||
let commit_body: Value = latest_commit_res.json().await.unwrap();
|
||||
let root_cid = commit_body["cid"].as_str().unwrap();
|
||||
|
||||
let get_blocks_url = format!(
|
||||
"{}/xrpc/com.atproto.sync.getBlocks?did={}&cids={}",
|
||||
base_url().await,
|
||||
did,
|
||||
root_cid
|
||||
);
|
||||
let get_blocks_res = client
|
||||
.get(&get_blocks_url)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get blocks");
|
||||
assert_eq!(get_blocks_res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
get_blocks_res
|
||||
.headers()
|
||||
.get("content-type")
|
||||
.and_then(|h| h.to_str().ok()),
|
||||
Some("application/vnd.ipld.car")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_apply_writes_batch_lifecycle() {
|
||||
let client = client();
|
||||
let (did, jwt) = setup_new_user("apply-writes-batch").await;
|
||||
|
||||
let now = Utc::now().to_rfc3339();
|
||||
let writes_payload = json!({
|
||||
"repo": did,
|
||||
"writes": [
|
||||
{
|
||||
"$type": "com.atproto.repo.applyWrites#create",
|
||||
"collection": "app.bsky.feed.post",
|
||||
"rkey": "batch-post-1",
|
||||
"value": {
|
||||
"$type": "app.bsky.feed.post",
|
||||
"text": "First batch post",
|
||||
"createdAt": now
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "com.atproto.repo.applyWrites#create",
|
||||
"collection": "app.bsky.feed.post",
|
||||
"rkey": "batch-post-2",
|
||||
"value": {
|
||||
"$type": "app.bsky.feed.post",
|
||||
"text": "Second batch post",
|
||||
"createdAt": now
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "com.atproto.repo.applyWrites#create",
|
||||
"collection": "app.bsky.actor.profile",
|
||||
"rkey": "self",
|
||||
"value": {
|
||||
"$type": "app.bsky.actor.profile",
|
||||
"displayName": "Batch User"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let apply_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.repo.applyWrites",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&jwt)
|
||||
.json(&writes_payload)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to apply writes");
|
||||
|
||||
assert_eq!(apply_res.status(), StatusCode::OK);
|
||||
|
||||
let get_post1 = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.repo.getRecord",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[
|
||||
("repo", did.as_str()),
|
||||
("collection", "app.bsky.feed.post"),
|
||||
("rkey", "batch-post-1"),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get post 1");
|
||||
assert_eq!(get_post1.status(), StatusCode::OK);
|
||||
let post1_body: Value = get_post1.json().await.unwrap();
|
||||
assert_eq!(post1_body["value"]["text"], "First batch post");
|
||||
|
||||
let get_post2 = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.repo.getRecord",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[
|
||||
("repo", did.as_str()),
|
||||
("collection", "app.bsky.feed.post"),
|
||||
("rkey", "batch-post-2"),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get post 2");
|
||||
assert_eq!(get_post2.status(), StatusCode::OK);
|
||||
|
||||
let get_profile = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.repo.getRecord",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[
|
||||
("repo", did.as_str()),
|
||||
("collection", "app.bsky.actor.profile"),
|
||||
("rkey", "self"),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get profile");
|
||||
assert_eq!(get_profile.status(), StatusCode::OK);
|
||||
let profile_body: Value = get_profile.json().await.unwrap();
|
||||
assert_eq!(profile_body["value"]["displayName"], "Batch User");
|
||||
|
||||
let update_writes = json!({
|
||||
"repo": did,
|
||||
"writes": [
|
||||
{
|
||||
"$type": "com.atproto.repo.applyWrites#update",
|
||||
"collection": "app.bsky.actor.profile",
|
||||
"rkey": "self",
|
||||
"value": {
|
||||
"$type": "app.bsky.actor.profile",
|
||||
"displayName": "Updated Batch User"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "com.atproto.repo.applyWrites#delete",
|
||||
"collection": "app.bsky.feed.post",
|
||||
"rkey": "batch-post-1"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let update_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.repo.applyWrites",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&jwt)
|
||||
.json(&update_writes)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to apply update writes");
|
||||
assert_eq!(update_res.status(), StatusCode::OK);
|
||||
|
||||
let get_updated_profile = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.repo.getRecord",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[
|
||||
("repo", did.as_str()),
|
||||
("collection", "app.bsky.actor.profile"),
|
||||
("rkey", "self"),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get updated profile");
|
||||
let updated_profile: Value = get_updated_profile.json().await.unwrap();
|
||||
assert_eq!(updated_profile["value"]["displayName"], "Updated Batch User");
|
||||
|
||||
let get_deleted_post = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.repo.getRecord",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[
|
||||
("repo", did.as_str()),
|
||||
("collection", "app.bsky.feed.post"),
|
||||
("rkey", "batch-post-1"),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to check deleted post");
|
||||
assert_eq!(
|
||||
get_deleted_post.status(),
|
||||
StatusCode::NOT_FOUND,
|
||||
"Batch-deleted post should be gone"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_handle_lifecycle() {
|
||||
let client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("resolve-test-{}.test", ts);
|
||||
let email = format!("resolve-test-{}@test.com", ts);
|
||||
|
||||
let create_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
base_url().await
|
||||
))
|
||||
.json(&json!({
|
||||
"handle": handle,
|
||||
"email": email,
|
||||
"password": "resolve-test-pw"
|
||||
}))
|
||||
.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();
|
||||
|
||||
let resolve_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.identity.resolveHandle",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[("handle", handle.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to resolve handle");
|
||||
|
||||
assert_eq!(resolve_res.status(), StatusCode::OK);
|
||||
let resolve_body: Value = resolve_res.json().await.unwrap();
|
||||
assert_eq!(resolve_body["did"], did);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_service_auth_lifecycle() {
|
||||
let client = client();
|
||||
let (did, jwt) = setup_new_user("service-auth-test").await;
|
||||
|
||||
let service_auth_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.server.getServiceAuth",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[
|
||||
("aud", "did:web:api.bsky.app"),
|
||||
("lxm", "com.atproto.repo.uploadBlob"),
|
||||
])
|
||||
.bearer_auth(&jwt)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get service auth");
|
||||
|
||||
assert_eq!(service_auth_res.status(), StatusCode::OK);
|
||||
let auth_body: Value = service_auth_res.json().await.unwrap();
|
||||
let service_token = auth_body["token"].as_str().expect("No token in response");
|
||||
|
||||
let parts: Vec<&str> = service_token.split('.').collect();
|
||||
assert_eq!(parts.len(), 3, "Service token should be a valid JWT");
|
||||
|
||||
let payload_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
|
||||
.decode(parts[1])
|
||||
.expect("Failed to decode JWT payload");
|
||||
let claims: Value = serde_json::from_slice(&payload_bytes).expect("Invalid JWT payload");
|
||||
|
||||
assert_eq!(claims["iss"], did);
|
||||
assert_eq!(claims["aud"], "did:web:api.bsky.app");
|
||||
assert_eq!(claims["lxm"], "com.atproto.repo.uploadBlob");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_moderation_report_lifecycle() {
|
||||
let client = client();
|
||||
let (alice_did, alice_jwt) = setup_new_user("alice-report").await;
|
||||
let (bob_did, bob_jwt) = setup_new_user("bob-report").await;
|
||||
|
||||
let (post_uri, post_cid) =
|
||||
create_post(&client, &bob_did, &bob_jwt, "This is a reportable post").await;
|
||||
|
||||
let report_payload = json!({
|
||||
"reasonType": "com.atproto.moderation.defs#reasonSpam",
|
||||
"reason": "This looks like spam to me",
|
||||
"subject": {
|
||||
"$type": "com.atproto.repo.strongRef",
|
||||
"uri": post_uri,
|
||||
"cid": post_cid
|
||||
}
|
||||
});
|
||||
|
||||
let report_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.moderation.createReport",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&alice_jwt)
|
||||
.json(&report_payload)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to create report");
|
||||
|
||||
assert_eq!(report_res.status(), StatusCode::OK);
|
||||
let report_body: Value = report_res.json().await.unwrap();
|
||||
assert!(report_body["id"].is_number(), "Report should have an ID");
|
||||
assert_eq!(report_body["reasonType"], "com.atproto.moderation.defs#reasonSpam");
|
||||
assert_eq!(report_body["reportedBy"], alice_did);
|
||||
|
||||
let account_report_payload = json!({
|
||||
"reasonType": "com.atproto.moderation.defs#reasonOther",
|
||||
"reason": "Suspicious account activity",
|
||||
"subject": {
|
||||
"$type": "com.atproto.admin.defs#repoRef",
|
||||
"did": bob_did
|
||||
}
|
||||
});
|
||||
|
||||
let account_report_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.moderation.createReport",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&alice_jwt)
|
||||
.json(&account_report_payload)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to create account report");
|
||||
|
||||
assert_eq!(account_report_res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
@@ -98,3 +98,69 @@ async fn test_proxy_auth_signing() {
|
||||
assert_eq!(claims["aud"], upstream_url);
|
||||
assert_eq!(claims["lxm"], "com.example.signed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_proxy_post_with_body() {
|
||||
let app_url = common::base_url().await;
|
||||
let (upstream_url, mut rx) = spawn_mock_upstream().await;
|
||||
let client = Client::new();
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"text": "Hello from proxy test",
|
||||
"createdAt": "2024-01-01T00:00:00Z"
|
||||
});
|
||||
|
||||
let res = client
|
||||
.post(format!("{}/xrpc/com.example.postMethod", app_url))
|
||||
.header("atproto-proxy", &upstream_url)
|
||||
.header("Authorization", "Bearer test-token")
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
|
||||
let (method, uri, auth) = rx.recv().await.expect("Upstream should receive request");
|
||||
assert_eq!(method, "POST");
|
||||
assert_eq!(uri, "/xrpc/com.example.postMethod");
|
||||
assert_eq!(auth, Some("Bearer test-token".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_proxy_with_query_params() {
|
||||
let app_url = common::base_url().await;
|
||||
let (upstream_url, mut rx) = spawn_mock_upstream().await;
|
||||
let client = Client::new();
|
||||
|
||||
let res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.example.query?repo=did:plc:test&collection=app.bsky.feed.post&limit=50",
|
||||
app_url
|
||||
))
|
||||
.header("atproto-proxy", &upstream_url)
|
||||
.header("Authorization", "Bearer test-token")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
|
||||
let (method, uri, _auth) = rx.recv().await.expect("Upstream should receive request");
|
||||
assert_eq!(method, "GET");
|
||||
assert!(
|
||||
uri.contains("repo=did") || uri.contains("repo=did%3Aplc%3Atest"),
|
||||
"URI should contain repo param, got: {}",
|
||||
uri
|
||||
);
|
||||
assert!(
|
||||
uri.contains("collection=app.bsky.feed.post") || uri.contains("collection=app.bsky"),
|
||||
"URI should contain collection param, got: {}",
|
||||
uri
|
||||
);
|
||||
assert!(
|
||||
uri.contains("limit=50"),
|
||||
"URI should contain limit param, got: {}",
|
||||
uri
|
||||
);
|
||||
}
|
||||
|
||||
+203
@@ -3,6 +3,7 @@ use common::*;
|
||||
use reqwest::StatusCode;
|
||||
use reqwest::header;
|
||||
use serde_json::Value;
|
||||
use chrono;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_latest_commit_success() {
|
||||
@@ -352,3 +353,205 @@ async fn test_request_crawl() {
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_repo_success() {
|
||||
let client = client();
|
||||
let (access_jwt, did) = create_account_and_login(&client).await;
|
||||
|
||||
let post_payload = serde_json::json!({
|
||||
"repo": did,
|
||||
"collection": "app.bsky.feed.post",
|
||||
"record": {
|
||||
"$type": "app.bsky.feed.post",
|
||||
"text": "Test post for getRepo",
|
||||
"createdAt": chrono::Utc::now().to_rfc3339()
|
||||
}
|
||||
});
|
||||
let _ = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.repo.createRecord",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.json(&post_payload)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to create record");
|
||||
|
||||
let params = [("did", did.as_str())];
|
||||
let res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getRepo",
|
||||
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("application/vnd.ipld.car")
|
||||
);
|
||||
let body = res.bytes().await.expect("Failed to get body");
|
||||
assert!(!body.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_repo_not_found() {
|
||||
let client = client();
|
||||
let params = [("did", "did:plc:nonexistent12345")];
|
||||
let res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getRepo",
|
||||
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_record_sync_success() {
|
||||
let client = client();
|
||||
let (access_jwt, did) = create_account_and_login(&client).await;
|
||||
|
||||
let post_payload = serde_json::json!({
|
||||
"repo": did,
|
||||
"collection": "app.bsky.feed.post",
|
||||
"record": {
|
||||
"$type": "app.bsky.feed.post",
|
||||
"text": "Test post for sync getRecord",
|
||||
"createdAt": chrono::Utc::now().to_rfc3339()
|
||||
}
|
||||
});
|
||||
let create_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.repo.createRecord",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.json(&post_payload)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to create record");
|
||||
|
||||
let create_body: Value = create_res.json().await.expect("Invalid JSON");
|
||||
let uri = create_body["uri"].as_str().expect("No URI");
|
||||
let rkey = uri.split('/').last().expect("Invalid URI");
|
||||
|
||||
let params = [
|
||||
("did", did.as_str()),
|
||||
("collection", "app.bsky.feed.post"),
|
||||
("rkey", rkey),
|
||||
];
|
||||
let res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getRecord",
|
||||
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("application/vnd.ipld.car")
|
||||
);
|
||||
let body = res.bytes().await.expect("Failed to get body");
|
||||
assert!(!body.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_record_sync_not_found() {
|
||||
let client = client();
|
||||
let (_, did) = create_account_and_login(&client).await;
|
||||
|
||||
let params = [
|
||||
("did", did.as_str()),
|
||||
("collection", "app.bsky.feed.post"),
|
||||
("rkey", "nonexistent12345"),
|
||||
];
|
||||
let res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getRecord",
|
||||
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"], "RecordNotFound");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_blocks_success() {
|
||||
let client = client();
|
||||
let (_, did) = create_account_and_login(&client).await;
|
||||
|
||||
let params = [("did", did.as_str())];
|
||||
let latest_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getLatestCommit",
|
||||
base_url().await
|
||||
))
|
||||
.query(¶ms)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get latest commit");
|
||||
|
||||
let latest_body: Value = latest_res.json().await.expect("Invalid JSON");
|
||||
let root_cid = latest_body["cid"].as_str().expect("No CID");
|
||||
|
||||
let url = format!(
|
||||
"{}/xrpc/com.atproto.sync.getBlocks?did={}&cids={}",
|
||||
base_url().await,
|
||||
did,
|
||||
root_cid
|
||||
);
|
||||
let res = client
|
||||
.get(&url)
|
||||
.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("application/vnd.ipld.car")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_blocks_not_found() {
|
||||
let client = client();
|
||||
let url = format!(
|
||||
"{}/xrpc/com.atproto.sync.getBlocks?did=did:plc:nonexistent12345&cids=bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku",
|
||||
base_url().await
|
||||
);
|
||||
let res = client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
|
||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user