More work on the pds notifs

This commit is contained in:
lewis
2025-12-16 18:00:44 +02:00
parent c24a942a28
commit e2bfcdb74f
57 changed files with 1861 additions and 442 deletions
+221
View File
@@ -0,0 +1,221 @@
mod common;
use common::{base_url, client, create_account_and_login, get_db_connection_string};
use bspds::notifications::{NewNotification, NotificationType, enqueue_notification};
use serde_json::{Value, json};
use sqlx::PgPool;
async fn get_pool() -> PgPool {
let conn_str = get_db_connection_string().await;
sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.connect(&conn_str)
.await
.expect("Failed to connect to test database")
}
#[tokio::test]
async fn test_get_notification_history() {
let client = client();
let base = base_url().await;
let pool = get_pool().await;
let (token, did) = create_account_and_login(&client).await;
let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
.fetch_one(&pool)
.await
.expect("User not found");
for i in 0..3 {
let notification = NewNotification::email(
user_id,
NotificationType::Welcome,
"test@example.com".to_string(),
format!("Subject {}", i),
format!("Body {}", i),
);
enqueue_notification(&pool, notification).await.expect("Failed to enqueue");
}
let resp = client
.get(format!("{}/xrpc/com.bspds.account.getNotificationHistory", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let body: Value = resp.json().await.unwrap();
let notifications = body["notifications"].as_array().unwrap();
assert_eq!(notifications.len(), 5);
assert_eq!(notifications[0]["subject"], "Subject 2");
assert_eq!(notifications[1]["subject"], "Subject 1");
assert_eq!(notifications[2]["subject"], "Subject 0");
}
#[tokio::test]
async fn test_verify_channel_discord() {
let client = client();
let base = base_url().await;
let (token, did) = create_account_and_login(&client).await;
let prefs = json!({
"discordId": "123456789"
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.updateNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let body: Value = resp.json().await.unwrap();
assert!(body["verificationRequired"].as_array().unwrap().contains(&json!("discord")));
let pool = get_pool().await;
let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
.fetch_one(&pool)
.await
.expect("User not found");
let code: String = sqlx::query_scalar!(
"SELECT code FROM channel_verifications WHERE user_id = $1 AND channel = 'discord'",
user_id
)
.fetch_one(&pool)
.await
.expect("Verification code not found");
let input = json!({
"channel": "discord",
"code": code
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base))
.header("Authorization", format!("Bearer {}", token))
.json(&input)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let resp = client
.get(format!("{}/xrpc/com.bspds.account.getNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
.unwrap();
let body: Value = resp.json().await.unwrap();
assert_eq!(body["discordVerified"], true);
assert_eq!(body["discordId"], "123456789");
}
#[tokio::test]
async fn test_verify_channel_invalid_code() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let prefs = json!({
"telegramUsername": "testuser"
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.updateNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let input = json!({
"channel": "telegram",
"code": "000000"
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base))
.header("Authorization", format!("Bearer {}", token))
.json(&input)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 400);
}
#[tokio::test]
async fn test_verify_channel_not_set() {
let client = client();
let base = base_url().await;
let (token, _did) = create_account_and_login(&client).await;
let input = json!({
"channel": "signal",
"code": "123456"
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base))
.header("Authorization", format!("Bearer {}", token))
.json(&input)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 400);
}
#[tokio::test]
async fn test_update_email_via_notification_prefs() {
let client = client();
let base = base_url().await;
let pool = get_pool().await;
let (token, did) = create_account_and_login(&client).await;
let prefs = json!({
"email": "newemail@example.com"
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.updateNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.json(&prefs)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let body: Value = resp.json().await.unwrap();
assert!(body["verificationRequired"].as_array().unwrap().contains(&json!("email")));
let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
.fetch_one(&pool)
.await
.expect("User not found");
let code: String = sqlx::query_scalar!(
"SELECT code FROM channel_verifications WHERE user_id = $1 AND channel = 'email'",
user_id
)
.fetch_one(&pool)
.await
.expect("Verification code not found");
let input = json!({
"channel": "email",
"code": code
});
let resp = client
.post(format!("{}/xrpc/com.bspds.account.confirmChannelVerification", base))
.header("Authorization", format!("Bearer {}", token))
.json(&input)
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let resp = client
.get(format!("{}/xrpc/com.bspds.account.getNotificationPrefs", base))
.header("Authorization", format!("Bearer {}", token))
.send()
.await
.unwrap();
let body: Value = resp.json().await.unwrap();
assert_eq!(body["email"], "newemail@example.com");
}
+41
View File
@@ -0,0 +1,41 @@
mod common;
use common::{base_url, client, create_account_and_login};
use serde_json::Value;
#[tokio::test]
async fn test_get_server_stats() {
let client = client();
let base = base_url().await;
let (token1, _) = create_account_and_login(&client).await;
let (_, _) = create_account_and_login(&client).await;
let resp = client
.get(format!("{}/xrpc/com.bspds.admin.getServerStats", base))
.header("Authorization", format!("Bearer {}", token1))
.send()
.await
.unwrap();
assert_eq!(resp.status(), 200);
let body: Value = resp.json().await.unwrap();
let user_count = body["userCount"].as_i64().unwrap();
assert!(user_count >= 2);
assert!(body["repoCount"].is_number());
assert!(body["recordCount"].is_number());
assert!(body["blobStorageBytes"].is_number());
}
#[tokio::test]
async fn test_get_server_stats_no_auth() {
let client = client();
let base = base_url().await;
let resp = client
.get(format!("{}/xrpc/com.bspds.admin.getServerStats", base))
.send()
.await
.unwrap();
assert_eq!(resp.status(), 401);
}
+9 -6
View File
@@ -79,6 +79,9 @@ pub async fn base_url() -> &'static str {
SERVER_URL.get_or_init(|| {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
unsafe {
std::env::set_var("BSPDS_ALLOW_INSECURE_SECRETS", "1");
}
if std::env::var("DOCKER_HOST").is_err() {
if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") {
let podman_sock = std::path::Path::new(&runtime_dir).join("podman/podman.sock");
@@ -406,13 +409,13 @@ pub async fn verify_new_account(client: &Client, did: &str) -> String {
.await
.expect("Failed to connect to test database");
let verification_code: String = sqlx::query_scalar!(
"SELECT email_confirmation_code FROM users WHERE did = $1",
"SELECT code FROM channel_verifications WHERE user_id = (SELECT id FROM users WHERE did = $1) AND channel = 'email'",
did
)
.fetch_one(&pool)
.await
.expect("Failed to get verification code")
.expect("No verification code found");
.expect("Failed to get verification code");
let confirm_payload = json!({
"did": did,
"verificationCode": verification_code
@@ -548,13 +551,13 @@ pub async fn create_account_and_login(client: &Client) -> (String, String) {
.await
.expect("Failed to connect to test database");
let verification_code: String = sqlx::query_scalar!(
"SELECT email_confirmation_code FROM users WHERE did = $1",
"SELECT code FROM channel_verifications WHERE user_id = (SELECT id FROM users WHERE did = $1) AND channel = 'email'",
&did
)
.fetch_one(&pool)
.await
.expect("Failed to get verification code")
.expect("No verification code found");
.expect("Failed to get verification code");
let confirm_payload = json!({
"did": did,
"verificationCode": verification_code
+34 -19
View File
@@ -59,19 +59,20 @@ async fn test_email_update_flow_success() {
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.expect("Invalid JSON");
assert_eq!(body["tokenRequired"], true);
let user = sqlx::query!(
"SELECT email_pending_verification, email_confirmation_code, email FROM users WHERE handle = $1",
let verification = sqlx::query!(
"SELECT pending_identifier, code FROM channel_verifications WHERE user_id = (SELECT id FROM users WHERE handle = $1) AND channel = 'email'",
handle
)
.fetch_one(&pool)
.await
.expect("User not found");
.expect("Verification not found");
assert_eq!(
user.email_pending_verification.as_deref(),
verification.pending_identifier.as_deref(),
Some(new_email.as_str())
);
assert!(user.email_confirmation_code.is_some());
let code = user.email_confirmation_code.unwrap();
let code = verification.code;
let res = client
.post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url))
.bearer_auth(&access_jwt)
@@ -84,15 +85,22 @@ async fn test_email_update_flow_success() {
.expect("Failed to confirm email");
assert_eq!(res.status(), StatusCode::OK);
let user = sqlx::query!(
"SELECT email, email_pending_verification, email_confirmation_code FROM users WHERE handle = $1",
"SELECT email FROM users WHERE handle = $1",
handle
)
.fetch_one(&pool)
.await
.expect("User not found");
assert_eq!(user.email, Some(new_email));
assert!(user.email_pending_verification.is_none());
assert!(user.email_confirmation_code.is_none());
let verification = sqlx::query!(
"SELECT code FROM channel_verifications WHERE user_id = (SELECT id FROM users WHERE handle = $1) AND channel = 'email'",
handle
)
.fetch_optional(&pool)
.await
.expect("DB error");
assert!(verification.is_none());
}
#[tokio::test]
@@ -174,14 +182,14 @@ async fn test_confirm_email_wrong_email() {
.await
.expect("Failed to request email update");
assert_eq!(res.status(), StatusCode::OK);
let user = sqlx::query!(
"SELECT email_confirmation_code FROM users WHERE handle = $1",
let verification = sqlx::query!(
"SELECT code FROM channel_verifications WHERE user_id = (SELECT id FROM users WHERE handle = $1) AND channel = 'email'",
handle
)
.fetch_one(&pool)
.await
.expect("User not found");
let code = user.email_confirmation_code.unwrap();
.expect("Verification not found");
let code = verification.code;
let res = client
.post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url))
.bearer_auth(&access_jwt)
@@ -293,14 +301,14 @@ async fn test_update_email_with_valid_token() {
.await
.expect("Failed to request email update");
assert_eq!(res.status(), StatusCode::OK);
let user = sqlx::query!(
"SELECT email_confirmation_code FROM users WHERE handle = $1",
let verification = sqlx::query!(
"SELECT code FROM channel_verifications WHERE user_id = (SELECT id FROM users WHERE handle = $1) AND channel = 'email'",
handle
)
.fetch_one(&pool)
.await
.expect("User not found");
let code = user.email_confirmation_code.unwrap();
.expect("Verification not found");
let code = verification.code;
let res = client
.post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
.bearer_auth(&access_jwt)
@@ -313,14 +321,21 @@ async fn test_update_email_with_valid_token() {
.expect("Failed to update email");
assert_eq!(res.status(), StatusCode::OK);
let user = sqlx::query!(
"SELECT email, email_pending_verification FROM users WHERE handle = $1",
"SELECT email FROM users WHERE handle = $1",
handle
)
.fetch_one(&pool)
.await
.expect("User not found");
assert_eq!(user.email, Some(new_email));
assert!(user.email_pending_verification.is_none());
let verification = sqlx::query!(
"SELECT code FROM channel_verifications WHERE user_id = (SELECT id FROM users WHERE handle = $1) AND channel = 'email'",
handle
)
.fetch_optional(&pool)
.await
.expect("DB error");
assert!(verification.is_none());
}
#[tokio::test]
+2 -3
View File
@@ -872,13 +872,12 @@ async fn test_jwt_security_refresh_token_replay_protection() {
.await
.expect("Failed to connect to test database");
let verification_code: String = sqlx::query_scalar!(
"SELECT email_confirmation_code FROM users WHERE did = $1",
"SELECT code FROM channel_verifications WHERE user_id = (SELECT id FROM users WHERE did = $1) AND channel = 'email'",
did
)
.fetch_one(&pool)
.await
.expect("Failed to get verification code")
.expect("No verification code found");
.expect("Failed to get verification code");
let confirm_res = http_client
.post(format!("{}/xrpc/com.atproto.server.confirmSignup", url))
.json(&json!({