mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-02-08 21:30:08 +00:00
Rest of lifecycle tests split out to other files
This commit is contained in:
40
.sqlx/query-2ff22a8c39914689d6cf215ba201fa4ced50b7a003ce01bf7603a7f125113447.json
generated
Normal file
40
.sqlx/query-2ff22a8c39914689d6cf215ba201fa4ced50b7a003ce01bf7603a7f125113447.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT code, available_uses, created_at, disabled\n FROM invite_codes\n WHERE created_by_user = $1\n ORDER BY created_at DESC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "code",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "available_uses",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "disabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "2ff22a8c39914689d6cf215ba201fa4ced50b7a003ce01bf7603a7f125113447"
|
||||
}
|
||||
14
.sqlx/query-3609b5817e4564b824b0c0f4fe32488ee7caed02cee08fb163e4914c5349eb11.json
generated
Normal file
14
.sqlx/query-3609b5817e4564b824b0c0f4fe32488ee7caed02cee08fb163e4914c5349eb11.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE users SET invites_disabled = TRUE WHERE did = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3609b5817e4564b824b0c0f4fe32488ee7caed02cee08fb163e4914c5349eb11"
|
||||
}
|
||||
15
.sqlx/query-411a7cff2d43612379903d6343da0761ae5b8b30a2fa1c89afb85047d4fbe3eb.json
generated
Normal file
15
.sqlx/query-411a7cff2d43612379903d6343da0761ae5b8b30a2fa1c89afb85047d4fbe3eb.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE records SET takedown_ref = $1 WHERE record_cid = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "411a7cff2d43612379903d6343da0761ae5b8b30a2fa1c89afb85047d4fbe3eb"
|
||||
}
|
||||
14
.sqlx/query-413c5b03501a399dca13f345fcae05770517091d73db93966853e944c68ee237.json
generated
Normal file
14
.sqlx/query-413c5b03501a399dca13f345fcae05770517091d73db93966853e944c68ee237.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE invite_codes SET disabled = TRUE WHERE created_by_user = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "413c5b03501a399dca13f345fcae05770517091d73db93966853e944c68ee237"
|
||||
}
|
||||
15
.sqlx/query-41d35cebdf29be500e30ef636ad96450620f71087c174e5a74446fcdb29a2ba8.json
generated
Normal file
15
.sqlx/query-41d35cebdf29be500e30ef636ad96450620f71087c174e5a74446fcdb29a2ba8.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE blobs SET takedown_ref = $1 WHERE cid = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "41d35cebdf29be500e30ef636ad96450620f71087c174e5a74446fcdb29a2ba8"
|
||||
}
|
||||
28
.sqlx/query-5d5442136932d4088873a935c41cb3a683c4771e4fb8c151b3fd5119fb6c1068.json
generated
Normal file
28
.sqlx/query-5d5442136932d4088873a935c41cb3a683c4771e4fb8c151b3fd5119fb6c1068.json
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT u.did, icu.used_at\n FROM invite_code_uses icu\n JOIN users u ON icu.used_by_user = u.id\n WHERE icu.code = $1\n ORDER BY icu.used_at DESC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "did",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "used_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5d5442136932d4088873a935c41cb3a683c4771e4fb8c151b3fd5119fb6c1068"
|
||||
}
|
||||
28
.sqlx/query-62942bd21d545eb15bfea4f46378b6c2ebfe12b8bc9e27c63a6c0f77a9105303.json
generated
Normal file
28
.sqlx/query-62942bd21d545eb15bfea4f46378b6c2ebfe12b8bc9e27c63a6c0f77a9105303.json
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT cid, takedown_ref FROM blobs WHERE cid = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "cid",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "takedown_ref",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "62942bd21d545eb15bfea4f46378b6c2ebfe12b8bc9e27c63a6c0f77a9105303"
|
||||
}
|
||||
22
.sqlx/query-6819c68a3c06083a826eb94271cc8ff0d4c2bbd33b9051f50a1a46ecc8d3e85b.json
generated
Normal file
22
.sqlx/query-6819c68a3c06083a826eb94271cc8ff0d4c2bbd33b9051f50a1a46ecc8d3e85b.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT did FROM users WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "did",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6819c68a3c06083a826eb94271cc8ff0d4c2bbd33b9051f50a1a46ecc8d3e85b"
|
||||
}
|
||||
14
.sqlx/query-78ed180c33b8f1f7a3adcd3dd0e7e5988ae1dbc2e10009df9fe44fb0fbbe95b3.json
generated
Normal file
14
.sqlx/query-78ed180c33b8f1f7a3adcd3dd0e7e5988ae1dbc2e10009df9fe44fb0fbbe95b3.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE users SET invites_disabled = FALSE WHERE did = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "78ed180c33b8f1f7a3adcd3dd0e7e5988ae1dbc2e10009df9fe44fb0fbbe95b3"
|
||||
}
|
||||
14
.sqlx/query-7b2d1d4ac06063e07a7c7a7d0fb434db08ce312eb2864405d7f96f4e985ed036.json
generated
Normal file
14
.sqlx/query-7b2d1d4ac06063e07a7c7a7d0fb434db08ce312eb2864405d7f96f4e985ed036.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE invite_codes SET disabled = TRUE WHERE code = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7b2d1d4ac06063e07a7c7a7d0fb434db08ce312eb2864405d7f96f4e985ed036"
|
||||
}
|
||||
34
.sqlx/query-7d1617283733986244b8129cdd14ec1d04510aa73e4ae350a54f57629b9eaff9.json
generated
Normal file
34
.sqlx/query-7d1617283733986244b8129cdd14ec1d04510aa73e4ae350a54f57629b9eaff9.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT did, deactivated_at, takedown_ref FROM users WHERE did = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "did",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "deactivated_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "takedown_ref",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7d1617283733986244b8129cdd14ec1d04510aa73e4ae350a54f57629b9eaff9"
|
||||
}
|
||||
16
.sqlx/query-bbe639bb24cc1bb3cc144baae263e7e3411e185bf7c91751ee1046c64a81df52.json
generated
Normal file
16
.sqlx/query-bbe639bb24cc1bb3cc144baae263e7e3411e185bf7c91751ee1046c64a81df52.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO invite_codes (code, available_uses, created_by_user) VALUES ($1, $2, $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bbe639bb24cc1bb3cc144baae263e7e3411e185bf7c91751ee1046c64a81df52"
|
||||
}
|
||||
15
.sqlx/query-cd25ddc034a51748f699e2fcd1312691123aee9904eb2ee4073ed0f2c8c49bf9.json
generated
Normal file
15
.sqlx/query-cd25ddc034a51748f699e2fcd1312691123aee9904eb2ee4073ed0f2c8c49bf9.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE users SET takedown_ref = $1 WHERE did = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cd25ddc034a51748f699e2fcd1312691123aee9904eb2ee4073ed0f2c8c49bf9"
|
||||
}
|
||||
22
.sqlx/query-da0e9a9edad3895ed5015b52335f5a0256e7bdc6c79e6faa927414d68800404c.json
generated
Normal file
22
.sqlx/query-da0e9a9edad3895ed5015b52335f5a0256e7bdc6c79e6faa927414d68800404c.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT invites_disabled FROM users WHERE did = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "invites_disabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "da0e9a9edad3895ed5015b52335f5a0256e7bdc6c79e6faa927414d68800404c"
|
||||
}
|
||||
28
.sqlx/query-fbc8ab04fe5e06d6e6de9a4eeaabee8af9ee887812bcfe5893df1c7e682747c1.json
generated
Normal file
28
.sqlx/query-fbc8ab04fe5e06d6e6de9a4eeaabee8af9ee887812bcfe5893df1c7e682747c1.json
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT r.id, r.takedown_ref FROM records r WHERE r.record_cid = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "takedown_ref",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "fbc8ab04fe5e06d6e6de9a4eeaabee8af9ee887812bcfe5893df1c7e682747c1"
|
||||
}
|
||||
2414
tests/lifecycle.rs
2414
tests/lifecycle.rs
File diff suppressed because it is too large
Load Diff
@@ -304,3 +304,142 @@ async fn test_app_password_lifecycle() {
|
||||
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");
|
||||
}
|
||||
|
||||
#[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");
|
||||
|
||||
use base64::Engine;
|
||||
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");
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use helpers::*;
|
||||
use reqwest::StatusCode;
|
||||
use serde_json::{Value, json};
|
||||
use std::time::Duration;
|
||||
use chrono::Utc;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_social_flow_lifecycle() {
|
||||
@@ -414,3 +415,102 @@ async fn test_mutual_follow_lifecycle() {
|
||||
let bob_feed = bob_tl["feed"].as_array().unwrap();
|
||||
assert_eq!(bob_feed.len(), 1, "Bob should see Alice's 1 post");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_account_to_post_full_lifecycle() {
|
||||
let client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("fullcycle-{}.test", ts);
|
||||
let email = format!("fullcycle-{}@test.com", ts);
|
||||
let password = "fullcycle-password";
|
||||
|
||||
let create_account_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_account_res.status(), StatusCode::OK);
|
||||
let account_body: Value = create_account_res.json().await.unwrap();
|
||||
let did = account_body["did"].as_str().unwrap().to_string();
|
||||
let access_jwt = account_body["accessJwt"].as_str().unwrap().to_string();
|
||||
|
||||
let get_session_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.server.getSession",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get session");
|
||||
|
||||
assert_eq!(get_session_res.status(), StatusCode::OK);
|
||||
let session_body: Value = get_session_res.json().await.unwrap();
|
||||
assert_eq!(session_body["did"], did);
|
||||
assert_eq!(session_body["handle"], handle);
|
||||
|
||||
let profile_res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.repo.putRecord",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.json(&json!({
|
||||
"repo": did,
|
||||
"collection": "app.bsky.actor.profile",
|
||||
"rkey": "self",
|
||||
"record": {
|
||||
"$type": "app.bsky.actor.profile",
|
||||
"displayName": "Full Cycle User"
|
||||
}
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to create profile");
|
||||
|
||||
assert_eq!(profile_res.status(), StatusCode::OK);
|
||||
|
||||
let (post_uri, post_cid) = create_post(&client, &did, &access_jwt, "My first post!").await;
|
||||
|
||||
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_uri.split('/').last().unwrap()),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get post");
|
||||
|
||||
assert_eq!(get_post_res.status(), StatusCode::OK);
|
||||
|
||||
create_like(&client, &did, &access_jwt, &post_uri, &post_cid).await;
|
||||
|
||||
let describe_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.repo.describeRepo",
|
||||
base_url().await
|
||||
))
|
||||
.query(&[("repo", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to describe repo");
|
||||
|
||||
assert_eq!(describe_res.status(), StatusCode::OK);
|
||||
let describe_body: Value = describe_res.json().await.unwrap();
|
||||
assert_eq!(describe_body["did"], did);
|
||||
assert_eq!(describe_body["handle"], handle);
|
||||
}
|
||||
67
tests/moderation.rs
Normal file
67
tests/moderation.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
mod common;
|
||||
mod helpers;
|
||||
|
||||
use common::*;
|
||||
use helpers::*;
|
||||
|
||||
use reqwest::StatusCode;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
#[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);
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
mod common;
|
||||
mod helpers;
|
||||
use common::*;
|
||||
use helpers::*;
|
||||
|
||||
use reqwest::StatusCode;
|
||||
use serde_json::Value;
|
||||
use reqwest::header;
|
||||
use serde_json::{Value, json};
|
||||
use chrono::Utc;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_latest_commit_success() {
|
||||
@@ -429,3 +434,267 @@ async fn test_get_blocks_not_found() {
|
||||
|
||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[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(std::time::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")
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user