Actor preferences

This commit is contained in:
lewis
2025-12-11 23:23:55 +02:00
parent 17a7f1dc2b
commit 2eb67eb688
25 changed files with 1136 additions and 90 deletions
+375
View File
@@ -0,0 +1,375 @@
mod common;
use common::{base_url, client, create_account_and_login};
use serde_json::{json, Value};
#[tokio::test]
async fn test_get_preferences_empty() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let resp = client
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let body: Value = resp.json().await.unwrap();
assert!(body.get("preferences").is_some());
assert!(body["preferences"].as_array().unwrap().is_empty());
}
#[tokio::test]
async fn test_get_preferences_no_auth() {
let client = client();
let base = base_url().await;
let resp = client
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
.send()
.await
.unwrap();
assert_eq!(resp.status(), 401);
}
#[tokio::test]
async fn test_put_preferences_success() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let prefs = json!({
"preferences": [
{
"$type": "app.bsky.actor.defs#adultContentPref",
"enabled": true
},
{
"$type": "app.bsky.actor.defs#contentLabelPref",
"label": "nsfw",
"visibility": "warn"
}
]
});
let resp = client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let resp = client
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let body: Value = resp.json().await.unwrap();
let prefs_arr = body["preferences"].as_array().unwrap();
assert_eq!(prefs_arr.len(), 2);
let adult_pref = prefs_arr.iter().find(|p| {
p.get("$type").and_then(|t| t.as_str()) == Some("app.bsky.actor.defs#adultContentPref")
});
assert!(adult_pref.is_some());
assert_eq!(adult_pref.unwrap()["enabled"], true);
}
#[tokio::test]
async fn test_put_preferences_no_auth() {
let client = client();
let base = base_url().await;
let prefs = json!({
"preferences": []
});
let resp = client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 401);
}
#[tokio::test]
async fn test_put_preferences_missing_type() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let prefs = json!({
"preferences": [
{
"enabled": true
}
]
});
let resp = client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 400);
let body: Value = resp.json().await.unwrap();
assert_eq!(body["error"], "InvalidRequest");
}
#[tokio::test]
async fn test_put_preferences_invalid_namespace() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let prefs = json!({
"preferences": [
{
"$type": "com.example.somePref",
"value": "test"
}
]
});
let resp = client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 400);
let body: Value = resp.json().await.unwrap();
assert_eq!(body["error"], "InvalidRequest");
}
#[tokio::test]
async fn test_put_preferences_read_only_rejected() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let prefs = json!({
"preferences": [
{
"$type": "app.bsky.actor.defs#declaredAgePref",
"isOverAge18": true
}
]
});
let resp = client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 400);
let body: Value = resp.json().await.unwrap();
assert_eq!(body["error"], "InvalidRequest");
}
#[tokio::test]
async fn test_put_preferences_replaces_all() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let prefs1 = json!({
"preferences": [
{
"$type": "app.bsky.actor.defs#adultContentPref",
"enabled": true
},
{
"$type": "app.bsky.actor.defs#contentLabelPref",
"label": "nsfw",
"visibility": "warn"
}
]
});
client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs1)
.send()
.await
.unwrap();
let prefs2 = json!({
"preferences": [
{
"$type": "app.bsky.actor.defs#threadViewPref",
"sort": "newest"
}
]
});
client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs2)
.send()
.await
.unwrap();
let resp = client
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let body: Value = resp.json().await.unwrap();
let prefs_arr = body["preferences"].as_array().unwrap();
assert_eq!(prefs_arr.len(), 1);
assert_eq!(prefs_arr[0]["$type"], "app.bsky.actor.defs#threadViewPref");
}
#[tokio::test]
async fn test_put_preferences_saved_feeds() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let prefs = json!({
"preferences": [
{
"$type": "app.bsky.actor.defs#savedFeedsPrefV2",
"items": [
{
"type": "feed",
"value": "at://did:plc:example/app.bsky.feed.generator/my-feed",
"pinned": true
}
]
}
]
});
let resp = client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let resp = client
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let body: Value = resp.json().await.unwrap();
let prefs_arr = body["preferences"].as_array().unwrap();
assert_eq!(prefs_arr.len(), 1);
let saved_feeds = &prefs_arr[0];
assert_eq!(saved_feeds["$type"], "app.bsky.actor.defs#savedFeedsPrefV2");
assert!(saved_feeds["items"].as_array().unwrap().len() == 1);
}
#[tokio::test]
async fn test_put_preferences_muted_words() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let prefs = json!({
"preferences": [
{
"$type": "app.bsky.actor.defs#mutedWordsPref",
"items": [
{
"value": "spoiler",
"targets": ["content", "tag"],
"actorTarget": "all"
}
]
}
]
});
let resp = client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let resp = client
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
.unwrap();
let body: Value = resp.json().await.unwrap();
let prefs_arr = body["preferences"].as_array().unwrap();
assert_eq!(prefs_arr[0]["$type"], "app.bsky.actor.defs#mutedWordsPref");
}
#[tokio::test]
async fn test_preferences_isolation_between_users() {
let client = client();
let base = base_url().await;
let (token1, _did1) = create_account_and_login(&client).await;
let (token2, _did2) = create_account_and_login(&client).await;
let prefs1 = json!({
"preferences": [
{
"$type": "app.bsky.actor.defs#adultContentPref",
"enabled": true
}
]
});
client
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
.header("Authorization", format!("Bearer {}", token1))
.json(&prefs1)
.send()
.await
.unwrap();
let resp = client
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
.header("Authorization", format!("Bearer {}", token2))
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let body: Value = resp.json().await.unwrap();
assert!(body["preferences"].as_array().unwrap().is_empty());
}
+7 -7
View File
@@ -1,7 +1,7 @@
mod common;
use bspds::notifications::{
enqueue_notification, enqueue_welcome_email, NewNotification, NotificationChannel,
enqueue_notification, enqueue_welcome, NewNotification, NotificationChannel,
NotificationStatus, NotificationType,
};
use sqlx::PgPool;
@@ -64,19 +64,19 @@ async fn test_enqueue_notification() {
}
#[tokio::test]
async fn test_enqueue_welcome_email() {
async fn test_enqueue_welcome() {
let pool = get_pool().await;
let (_, did) = common::create_account_and_login(&common::client()).await;
let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
let user_row = sqlx::query!("SELECT id, email, handle FROM users WHERE did = $1", did)
.fetch_one(&pool)
.await
.expect("User not found");
let notification_id = enqueue_welcome_email(&pool, user_id, "user@example.com", "testhandle", "example.com")
let notification_id = enqueue_welcome(&pool, user_row.id, "example.com")
.await
.expect("Failed to enqueue welcome email");
.expect("Failed to enqueue welcome notification");
let row = sqlx::query!(
r#"
@@ -92,9 +92,9 @@ async fn test_enqueue_welcome_email() {
.await
.expect("Notification not found");
assert_eq!(row.recipient, "user@example.com");
assert_eq!(row.recipient, user_row.email);
assert_eq!(row.subject.as_deref(), Some("Welcome to example.com"));
assert!(row.body.contains("@testhandle"));
assert!(row.body.contains(&format!("@{}", user_row.handle)));
assert_eq!(row.notification_type, NotificationType::Welcome);
}
+1 -1
View File
@@ -1428,7 +1428,7 @@ async fn test_state_with_special_chars() {
let mock_client = setup_mock_client_metadata(redirect_uri).await;
let client_id = mock_client.uri();
let (code_verifier, code_challenge) = generate_pkce();
let (_code_verifier, code_challenge) = generate_pkce();
let special_state = "state=with&special=chars&plus+more";
let par_body: Value = http_client
-1
View File
@@ -11,7 +11,6 @@ fn create_dpop_proof(
iat_offset_secs: i64,
) -> String {
use p256::ecdsa::{SigningKey, Signature, signature::Signer};
use p256::elliptic_curve::sec1::ToEncodedPoint;
let signing_key = SigningKey::random(&mut rand::thread_rng());
let verifying_key = signing_key.verifying_key();