this repo has no description

Admin endoints vs ref

lewis 9e57563a daa358e7

+64
.sqlx/query-13bea39e403ee15f13f877654c6677f7f2ad541edf72324231801ffead506031.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT id, did, handle, email, created_at, invites_disabled, email_verified, deactivated_at\n FROM users\n WHERE did = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "id", 9 + "type_info": "Uuid" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "did", 14 + "type_info": "Text" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "handle", 19 + "type_info": "Text" 20 + }, 21 + { 22 + "ordinal": 3, 23 + "name": "email", 24 + "type_info": "Text" 25 + }, 26 + { 27 + "ordinal": 4, 28 + "name": "created_at", 29 + "type_info": "Timestamptz" 30 + }, 31 + { 32 + "ordinal": 5, 33 + "name": "invites_disabled", 34 + "type_info": "Bool" 35 + }, 36 + { 37 + "ordinal": 6, 38 + "name": "email_verified", 39 + "type_info": "Bool" 40 + }, 41 + { 42 + "ordinal": 7, 43 + "name": "deactivated_at", 44 + "type_info": "Timestamptz" 45 + } 46 + ], 47 + "parameters": { 48 + "Left": [ 49 + "Text" 50 + ] 51 + }, 52 + "nullable": [ 53 + false, 54 + false, 55 + false, 56 + true, 57 + false, 58 + true, 59 + false, 60 + true 61 + ] 62 + }, 63 + "hash": "13bea39e403ee15f13f877654c6677f7f2ad541edf72324231801ffead506031" 64 + }
-40
.sqlx/query-176d30f31356a4d128764c9c2eece81f8079a29e40b07ba58adc4380d58068c8.json
··· 1 - { 2 - "db_name": "PostgreSQL", 3 - "query": "\n SELECT did, handle, email, created_at\n FROM users\n WHERE did = $1\n ", 4 - "describe": { 5 - "columns": [ 6 - { 7 - "ordinal": 0, 8 - "name": "did", 9 - "type_info": "Text" 10 - }, 11 - { 12 - "ordinal": 1, 13 - "name": "handle", 14 - "type_info": "Text" 15 - }, 16 - { 17 - "ordinal": 2, 18 - "name": "email", 19 - "type_info": "Text" 20 - }, 21 - { 22 - "ordinal": 3, 23 - "name": "created_at", 24 - "type_info": "Timestamptz" 25 - } 26 - ], 27 - "parameters": { 28 - "Left": [ 29 - "Text" 30 - ] 31 - }, 32 - "nullable": [ 33 - false, 34 - false, 35 - true, 36 - false 37 - ] 38 - }, 39 - "hash": "176d30f31356a4d128764c9c2eece81f8079a29e40b07ba58adc4380d58068c8" 40 - }
···
+22
.sqlx/query-1e034c36940110579d5ba3e6f64b4455a4945b4116dbd561e12269cf1df495b3.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT icu.code\n FROM invite_code_uses icu\n WHERE icu.used_by_user = $1\n LIMIT 1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "code", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "1e034c36940110579d5ba3e6f64b4455a4945b4116dbd561e12269cf1df495b3" 22 + }
+22
.sqlx/query-5a98e015997942835800fcd326e69b4f54b9830d0490c4f8841f8435478c57d3.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT code FROM invite_codes WHERE created_by_user = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "code", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "5a98e015997942835800fcd326e69b4f54b9830d0490c4f8841f8435478c57d3" 22 + }
+64
.sqlx/query-6df413951ea7648c77d8db2fe6e704370869816a3f47c86671dfe000b5961eee.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT id, did, handle, email, created_at, invites_disabled, email_verified, deactivated_at\n FROM users\n WHERE did = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "id", 9 + "type_info": "Uuid" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "did", 14 + "type_info": "Text" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "handle", 19 + "type_info": "Text" 20 + }, 21 + { 22 + "ordinal": 3, 23 + "name": "email", 24 + "type_info": "Text" 25 + }, 26 + { 27 + "ordinal": 4, 28 + "name": "created_at", 29 + "type_info": "Timestamptz" 30 + }, 31 + { 32 + "ordinal": 5, 33 + "name": "invites_disabled", 34 + "type_info": "Bool" 35 + }, 36 + { 37 + "ordinal": 6, 38 + "name": "email_verified", 39 + "type_info": "Bool" 40 + }, 41 + { 42 + "ordinal": 7, 43 + "name": "deactivated_at", 44 + "type_info": "Timestamptz" 45 + } 46 + ], 47 + "parameters": { 48 + "Left": [ 49 + "Text" 50 + ] 51 + }, 52 + "nullable": [ 53 + false, 54 + false, 55 + false, 56 + true, 57 + false, 58 + true, 59 + false, 60 + true 61 + ] 62 + }, 63 + "hash": "6df413951ea7648c77d8db2fe6e704370869816a3f47c86671dfe000b5961eee" 64 + }
-40
.sqlx/query-c2a90157c47bf1c36f08f4608932d214cc26b4794e0b922b1dae3dad18a7ddc0.json
··· 1 - { 2 - "db_name": "PostgreSQL", 3 - "query": "\n SELECT did, handle, email, created_at\n FROM users\n WHERE did = $1\n ", 4 - "describe": { 5 - "columns": [ 6 - { 7 - "ordinal": 0, 8 - "name": "did", 9 - "type_info": "Text" 10 - }, 11 - { 12 - "ordinal": 1, 13 - "name": "handle", 14 - "type_info": "Text" 15 - }, 16 - { 17 - "ordinal": 2, 18 - "name": "email", 19 - "type_info": "Text" 20 - }, 21 - { 22 - "ordinal": 3, 23 - "name": "created_at", 24 - "type_info": "Timestamptz" 25 - } 26 - ], 27 - "parameters": { 28 - "Left": [ 29 - "Text" 30 - ] 31 - }, 32 - "nullable": [ 33 - false, 34 - false, 35 - true, 36 - false 37 - ] 38 - }, 39 - "hash": "c2a90157c47bf1c36f08f4608932d214cc26b4794e0b922b1dae3dad18a7ddc0" 40 - }
···
+52
.sqlx/query-c3139484bba403cd256801e278fe95ae77634e79d14764dd8c3764886cf08eac.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT ic.code, ic.available_uses, ic.disabled, ic.for_account, ic.created_at, u.did as created_by\n FROM invite_codes ic\n JOIN users u ON ic.created_by_user = u.id\n WHERE ic.code = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "code", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "available_uses", 14 + "type_info": "Int4" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "disabled", 19 + "type_info": "Bool" 20 + }, 21 + { 22 + "ordinal": 3, 23 + "name": "for_account", 24 + "type_info": "Text" 25 + }, 26 + { 27 + "ordinal": 4, 28 + "name": "created_at", 29 + "type_info": "Timestamptz" 30 + }, 31 + { 32 + "ordinal": 5, 33 + "name": "created_by", 34 + "type_info": "Text" 35 + } 36 + ], 37 + "parameters": { 38 + "Left": [ 39 + "Text" 40 + ] 41 + }, 42 + "nullable": [ 43 + false, 44 + false, 45 + true, 46 + false, 47 + false, 48 + false 49 + ] 50 + }, 51 + "hash": "c3139484bba403cd256801e278fe95ae77634e79d14764dd8c3764886cf08eac" 52 + }
+28
.sqlx/query-c9f3d584c161b6492abc082bdbb563d40173a9a4983d6454dba4e02f7e0f8458.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT u.did as used_by, 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 ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "used_by", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "used_at", 14 + "type_info": "Timestamptz" 15 + } 16 + ], 17 + "parameters": { 18 + "Left": [ 19 + "Text" 20 + ] 21 + }, 22 + "nullable": [ 23 + false, 24 + false 25 + ] 26 + }, 27 + "hash": "c9f3d584c161b6492abc082bdbb563d40173a9a4983d6454dba4e02f7e0f8458" 28 + }
+2 -1
scripts/test-infra.sh
··· 41 -e POSTGRES_DB=postgres \ 42 -P \ 43 --label tranquil_pds_test=true \ 44 - postgres:18-alpine >/dev/null 45 echo "Starting MinIO..." 46 $CONTAINER_CMD run -d \ 47 --name "${CONTAINER_PREFIX}-minio" \
··· 41 -e POSTGRES_DB=postgres \ 42 -P \ 43 --label tranquil_pds_test=true \ 44 + postgres:18-alpine \ 45 + -c max_connections=500 >/dev/null 46 echo "Starting MinIO..." 47 $CONTAINER_CMD run -d \ 48 --name "${CONTAINER_PREFIX}-minio" \
+155 -20
src/api/admin/account/info.rs
··· 20 pub struct AccountInfo { 21 pub did: String, 22 pub handle: String, 23 pub email: Option<String>, 24 pub indexed_at: String, 25 pub invite_note: Option<String>, 26 pub invites_disabled: bool, 27 - pub email_verified_at: Option<String>, 28 pub deactivated_at: Option<String>, 29 } 30 31 #[derive(Serialize)] ··· 49 } 50 let result = sqlx::query!( 51 r#" 52 - SELECT did, handle, email, created_at 53 FROM users 54 WHERE did = $1 55 "#, ··· 58 .fetch_optional(&state.db) 59 .await; 60 match result { 61 - Ok(Some(row)) => ( 62 - StatusCode::OK, 63 - Json(AccountInfo { 64 - did: row.did, 65 - handle: row.handle, 66 - email: row.email, 67 - indexed_at: row.created_at.to_rfc3339(), 68 - invite_note: None, 69 - invites_disabled: false, 70 - email_verified_at: None, 71 - deactivated_at: None, 72 - }), 73 - ) 74 - .into_response(), 75 Ok(None) => ( 76 StatusCode::NOT_FOUND, 77 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), ··· 88 } 89 } 90 91 pub async fn get_account_infos( 92 State(state): State<AppState>, 93 _auth: BearerAuthAdmin, ··· 108 } 109 let result = sqlx::query!( 110 r#" 111 - SELECT did, handle, email, created_at 112 FROM users 113 WHERE did = $1 114 "#, ··· 117 .fetch_optional(&state.db) 118 .await; 119 if let Ok(Some(row)) = result { 120 infos.push(AccountInfo { 121 did: row.did, 122 handle: row.handle, 123 email: row.email, 124 indexed_at: row.created_at.to_rfc3339(), 125 invite_note: None, 126 - invites_disabled: false, 127 - email_verified_at: None, 128 - deactivated_at: None, 129 }); 130 } 131 }
··· 20 pub struct AccountInfo { 21 pub did: String, 22 pub handle: String, 23 + #[serde(skip_serializing_if = "Option::is_none")] 24 pub email: Option<String>, 25 pub indexed_at: String, 26 + #[serde(skip_serializing_if = "Option::is_none")] 27 pub invite_note: Option<String>, 28 pub invites_disabled: bool, 29 + #[serde(skip_serializing_if = "Option::is_none")] 30 + pub email_confirmed_at: Option<String>, 31 + #[serde(skip_serializing_if = "Option::is_none")] 32 pub deactivated_at: Option<String>, 33 + #[serde(skip_serializing_if = "Option::is_none")] 34 + pub invited_by: Option<InviteCodeInfo>, 35 + #[serde(skip_serializing_if = "Option::is_none")] 36 + pub invites: Option<Vec<InviteCodeInfo>>, 37 + } 38 + 39 + #[derive(Serialize, Clone)] 40 + #[serde(rename_all = "camelCase")] 41 + pub struct InviteCodeInfo { 42 + pub code: String, 43 + pub available: i32, 44 + pub disabled: bool, 45 + pub for_account: String, 46 + pub created_by: String, 47 + pub created_at: String, 48 + pub uses: Vec<InviteCodeUseInfo>, 49 + } 50 + 51 + #[derive(Serialize, Clone)] 52 + #[serde(rename_all = "camelCase")] 53 + pub struct InviteCodeUseInfo { 54 + pub used_by: String, 55 + pub used_at: String, 56 } 57 58 #[derive(Serialize)] ··· 76 } 77 let result = sqlx::query!( 78 r#" 79 + SELECT id, did, handle, email, created_at, invites_disabled, email_verified, deactivated_at 80 FROM users 81 WHERE did = $1 82 "#, ··· 85 .fetch_optional(&state.db) 86 .await; 87 match result { 88 + Ok(Some(row)) => { 89 + let invited_by = get_invited_by(&state.db, row.id).await; 90 + let invites = get_invites_for_user(&state.db, row.id).await; 91 + ( 92 + StatusCode::OK, 93 + Json(AccountInfo { 94 + did: row.did, 95 + handle: row.handle, 96 + email: row.email, 97 + indexed_at: row.created_at.to_rfc3339(), 98 + invite_note: None, 99 + invites_disabled: row.invites_disabled.unwrap_or(false), 100 + email_confirmed_at: if row.email_verified { 101 + Some(row.created_at.to_rfc3339()) 102 + } else { 103 + None 104 + }, 105 + deactivated_at: row.deactivated_at.map(|dt| dt.to_rfc3339()), 106 + invited_by, 107 + invites, 108 + }), 109 + ) 110 + .into_response() 111 + } 112 Ok(None) => ( 113 StatusCode::NOT_FOUND, 114 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), ··· 125 } 126 } 127 128 + async fn get_invited_by( 129 + db: &sqlx::PgPool, 130 + user_id: uuid::Uuid, 131 + ) -> Option<InviteCodeInfo> { 132 + let use_row = sqlx::query!( 133 + r#" 134 + SELECT icu.code 135 + FROM invite_code_uses icu 136 + WHERE icu.used_by_user = $1 137 + LIMIT 1 138 + "#, 139 + user_id 140 + ) 141 + .fetch_optional(db) 142 + .await 143 + .ok()??; 144 + get_invite_code_info(db, &use_row.code).await 145 + } 146 + 147 + async fn get_invites_for_user( 148 + db: &sqlx::PgPool, 149 + user_id: uuid::Uuid, 150 + ) -> Option<Vec<InviteCodeInfo>> { 151 + let codes = sqlx::query_scalar!( 152 + r#" 153 + SELECT code FROM invite_codes WHERE created_by_user = $1 154 + "#, 155 + user_id 156 + ) 157 + .fetch_all(db) 158 + .await 159 + .ok()?; 160 + if codes.is_empty() { 161 + return None; 162 + } 163 + let mut invites = Vec::new(); 164 + for code in codes { 165 + if let Some(info) = get_invite_code_info(db, &code).await { 166 + invites.push(info); 167 + } 168 + } 169 + if invites.is_empty() { 170 + None 171 + } else { 172 + Some(invites) 173 + } 174 + } 175 + 176 + async fn get_invite_code_info(db: &sqlx::PgPool, code: &str) -> Option<InviteCodeInfo> { 177 + let row = sqlx::query!( 178 + r#" 179 + SELECT ic.code, ic.available_uses, ic.disabled, ic.for_account, ic.created_at, u.did as created_by 180 + FROM invite_codes ic 181 + JOIN users u ON ic.created_by_user = u.id 182 + WHERE ic.code = $1 183 + "#, 184 + code 185 + ) 186 + .fetch_optional(db) 187 + .await 188 + .ok()??; 189 + let uses = sqlx::query!( 190 + r#" 191 + SELECT u.did as used_by, icu.used_at 192 + FROM invite_code_uses icu 193 + JOIN users u ON icu.used_by_user = u.id 194 + WHERE icu.code = $1 195 + "#, 196 + code 197 + ) 198 + .fetch_all(db) 199 + .await 200 + .ok()?; 201 + Some(InviteCodeInfo { 202 + code: row.code, 203 + available: row.available_uses, 204 + disabled: row.disabled.unwrap_or(false), 205 + for_account: row.for_account, 206 + created_by: row.created_by, 207 + created_at: row.created_at.to_rfc3339(), 208 + uses: uses 209 + .into_iter() 210 + .map(|u| InviteCodeUseInfo { 211 + used_by: u.used_by, 212 + used_at: u.used_at.to_rfc3339(), 213 + }) 214 + .collect(), 215 + }) 216 + } 217 + 218 pub async fn get_account_infos( 219 State(state): State<AppState>, 220 _auth: BearerAuthAdmin, ··· 235 } 236 let result = sqlx::query!( 237 r#" 238 + SELECT id, did, handle, email, created_at, invites_disabled, email_verified, deactivated_at 239 FROM users 240 WHERE did = $1 241 "#, ··· 244 .fetch_optional(&state.db) 245 .await; 246 if let Ok(Some(row)) = result { 247 + let invited_by = get_invited_by(&state.db, row.id).await; 248 + let invites = get_invites_for_user(&state.db, row.id).await; 249 infos.push(AccountInfo { 250 did: row.did, 251 handle: row.handle, 252 email: row.email, 253 indexed_at: row.created_at.to_rfc3339(), 254 invite_note: None, 255 + invites_disabled: row.invites_disabled.unwrap_or(false), 256 + email_confirmed_at: if row.email_verified { 257 + Some(row.created_at.to_rfc3339()) 258 + } else { 259 + None 260 + }, 261 + deactivated_at: row.deactivated_at.map(|dt| dt.to_rfc3339()), 262 + invited_by, 263 + invites, 264 }); 265 } 266 }
+13 -7
src/api/admin/account/search.rs
··· 12 13 #[derive(Deserialize)] 14 pub struct SearchAccountsParams { 15 pub handle: Option<String>, 16 pub cursor: Option<String>, 17 #[serde(default = "default_limit")] ··· 31 pub email: Option<String>, 32 pub indexed_at: String, 33 #[serde(skip_serializing_if = "Option::is_none")] 34 - pub email_verified_at: Option<String>, 35 #[serde(skip_serializing_if = "Option::is_none")] 36 pub deactivated_at: Option<String>, 37 #[serde(skip_serializing_if = "Option::is_none")] ··· 53 ) -> Response { 54 let limit = params.limit.clamp(1, 100); 55 let cursor_did = params.cursor.as_deref().unwrap_or(""); 56 let handle_filter = params.handle.as_deref().map(|h| format!("%{}%", h)); 57 let result = sqlx::query_as::< 58 _, ··· 63 chrono::DateTime<chrono::Utc>, 64 bool, 65 Option<chrono::DateTime<chrono::Utc>>, 66 ), 67 >( 68 r#" 69 - SELECT did, handle, email, created_at, email_verified, deactivated_at 70 FROM users 71 - WHERE did > $1 AND ($2::text IS NULL OR handle ILIKE $2) 72 ORDER BY did ASC 73 - LIMIT $3 74 "#, 75 ) 76 .bind(cursor_did) 77 .bind(&handle_filter) 78 .bind(limit + 1) 79 .fetch_all(&state.db) ··· 85 .into_iter() 86 .take(limit as usize) 87 .map( 88 - |(did, handle, email, created_at, email_verified, deactivated_at)| { 89 AccountView { 90 did: did.clone(), 91 handle, 92 email, 93 indexed_at: created_at.to_rfc3339(), 94 - email_verified_at: if email_verified { 95 Some(created_at.to_rfc3339()) 96 } else { 97 None 98 }, 99 deactivated_at: deactivated_at.map(|dt| dt.to_rfc3339()), 100 - invites_disabled: None, 101 } 102 }, 103 )
··· 12 13 #[derive(Deserialize)] 14 pub struct SearchAccountsParams { 15 + pub email: Option<String>, 16 pub handle: Option<String>, 17 pub cursor: Option<String>, 18 #[serde(default = "default_limit")] ··· 32 pub email: Option<String>, 33 pub indexed_at: String, 34 #[serde(skip_serializing_if = "Option::is_none")] 35 + pub email_confirmed_at: Option<String>, 36 #[serde(skip_serializing_if = "Option::is_none")] 37 pub deactivated_at: Option<String>, 38 #[serde(skip_serializing_if = "Option::is_none")] ··· 54 ) -> Response { 55 let limit = params.limit.clamp(1, 100); 56 let cursor_did = params.cursor.as_deref().unwrap_or(""); 57 + let email_filter = params.email.as_deref().map(|e| format!("%{}%", e)); 58 let handle_filter = params.handle.as_deref().map(|h| format!("%{}%", h)); 59 let result = sqlx::query_as::< 60 _, ··· 65 chrono::DateTime<chrono::Utc>, 66 bool, 67 Option<chrono::DateTime<chrono::Utc>>, 68 + Option<bool>, 69 ), 70 >( 71 r#" 72 + SELECT did, handle, email, created_at, email_verified, deactivated_at, invites_disabled 73 FROM users 74 + WHERE did > $1 75 + AND ($2::text IS NULL OR email ILIKE $2) 76 + AND ($3::text IS NULL OR handle ILIKE $3) 77 ORDER BY did ASC 78 + LIMIT $4 79 "#, 80 ) 81 .bind(cursor_did) 82 + .bind(&email_filter) 83 .bind(&handle_filter) 84 .bind(limit + 1) 85 .fetch_all(&state.db) ··· 91 .into_iter() 92 .take(limit as usize) 93 .map( 94 + |(did, handle, email, created_at, email_verified, deactivated_at, invites_disabled)| { 95 AccountView { 96 did: did.clone(), 97 handle, 98 email, 99 indexed_at: created_at.to_rfc3339(), 100 + email_confirmed_at: if email_verified { 101 Some(created_at.to_rfc3339()) 102 } else { 103 None 104 }, 105 deactivated_at: deactivated_at.map(|dt| dt.to_rfc3339()), 106 + invites_disabled, 107 } 108 }, 109 )
+10 -1
src/api/admin/account/update.rs
··· 8 }; 9 use serde::Deserialize; 10 use serde_json::json; 11 - use tracing::error; 12 13 #[derive(Deserialize)] 14 pub struct UpdateAccountEmailInput { ··· 128 let _ = state.cache.delete(&format!("handle:{}", old)).await; 129 } 130 let _ = state.cache.delete(&format!("handle:{}", handle)).await; 131 (StatusCode::OK, Json(json!({}))).into_response() 132 } 133 Err(e) => {
··· 8 }; 9 use serde::Deserialize; 10 use serde_json::json; 11 + use tracing::{error, warn}; 12 13 #[derive(Deserialize)] 14 pub struct UpdateAccountEmailInput { ··· 128 let _ = state.cache.delete(&format!("handle:{}", old)).await; 129 } 130 let _ = state.cache.delete(&format!("handle:{}", handle)).await; 131 + if let Err(e) = 132 + crate::api::repo::record::sequence_identity_event(&state, did, Some(&handle)).await 133 + { 134 + warn!("Failed to sequence identity event for admin handle update: {}", e); 135 + } 136 + if let Err(e) = crate::api::identity::did::update_plc_handle(&state, did, &handle).await 137 + { 138 + warn!("Failed to update PLC handle for admin handle update: {}", e); 139 + } 140 (StatusCode::OK, Json(json!({}))).into_response() 141 } 142 Err(e) => {
+24 -14
src/api/admin/status.rs
··· 135 } 136 } 137 if let Some(blob_cid) = &params.blob { 138 let blob = sqlx::query!( 139 "SELECT cid, takedown_ref FROM blobs WHERE cid = $1", 140 blob_cid ··· 152 Json(SubjectStatus { 153 subject: json!({ 154 "$type": "com.atproto.admin.defs#repoBlobRef", 155 - "did": "", 156 "cid": row.cid 157 }), 158 takedown, ··· 195 196 #[derive(Deserialize)] 197 pub struct StatusAttrInput { 198 - pub apply: bool, 199 pub r#ref: Option<String>, 200 } 201 ··· 221 } 222 }; 223 if let Some(takedown) = &input.takedown { 224 - let takedown_ref = if takedown.apply { 225 takedown.r#ref.clone() 226 } else { 227 None ··· 243 } 244 } 245 if let Some(deactivated) = &input.deactivated { 246 - let result = if deactivated.apply { 247 sqlx::query!( 248 "UPDATE users SET deactivated_at = NOW() WHERE did = $1", 249 did ··· 276 .into_response(); 277 } 278 if let Some(takedown) = &input.takedown { 279 - let status = if takedown.apply { 280 Some("takendown") 281 } else { 282 None ··· 284 if let Err(e) = crate::api::repo::record::sequence_account_event( 285 &state, 286 did, 287 - !takedown.apply, 288 status, 289 ) 290 .await ··· 293 } 294 } 295 if let Some(deactivated) = &input.deactivated { 296 - let status = if deactivated.apply { 297 Some("deactivated") 298 } else { 299 None ··· 301 if let Err(e) = crate::api::repo::record::sequence_account_event( 302 &state, 303 did, 304 - !deactivated.apply, 305 status, 306 ) 307 .await ··· 321 Json(json!({ 322 "subject": input.subject, 323 "takedown": input.takedown.as_ref().map(|t| json!({ 324 - "applied": t.apply, 325 "ref": t.r#ref 326 })), 327 "deactivated": input.deactivated.as_ref().map(|d| json!({ 328 - "applied": d.apply 329 })) 330 })), 331 ) ··· 336 let uri = input.subject.get("uri").and_then(|u| u.as_str()); 337 if let Some(uri) = uri { 338 if let Some(takedown) = &input.takedown { 339 - let takedown_ref = if takedown.apply { 340 takedown.r#ref.clone() 341 } else { 342 None ··· 365 Json(json!({ 366 "subject": input.subject, 367 "takedown": input.takedown.as_ref().map(|t| json!({ 368 - "applied": t.apply, 369 "ref": t.r#ref 370 })) 371 })), ··· 377 let cid = input.subject.get("cid").and_then(|c| c.as_str()); 378 if let Some(cid) = cid { 379 if let Some(takedown) = &input.takedown { 380 - let takedown_ref = if takedown.apply { 381 takedown.r#ref.clone() 382 } else { 383 None ··· 403 Json(json!({ 404 "subject": input.subject, 405 "takedown": input.takedown.as_ref().map(|t| json!({ 406 - "applied": t.apply, 407 "ref": t.r#ref 408 })) 409 })),
··· 135 } 136 } 137 if let Some(blob_cid) = &params.blob { 138 + let did = match &params.did { 139 + Some(d) => d, 140 + None => { 141 + return ( 142 + StatusCode::BAD_REQUEST, 143 + Json(json!({"error": "InvalidRequest", "message": "Must provide a did to request blob state"})), 144 + ) 145 + .into_response(); 146 + } 147 + }; 148 let blob = sqlx::query!( 149 "SELECT cid, takedown_ref FROM blobs WHERE cid = $1", 150 blob_cid ··· 162 Json(SubjectStatus { 163 subject: json!({ 164 "$type": "com.atproto.admin.defs#repoBlobRef", 165 + "did": did, 166 "cid": row.cid 167 }), 168 takedown, ··· 205 206 #[derive(Deserialize)] 207 pub struct StatusAttrInput { 208 + pub applied: bool, 209 pub r#ref: Option<String>, 210 } 211 ··· 231 } 232 }; 233 if let Some(takedown) = &input.takedown { 234 + let takedown_ref = if takedown.applied { 235 takedown.r#ref.clone() 236 } else { 237 None ··· 253 } 254 } 255 if let Some(deactivated) = &input.deactivated { 256 + let result = if deactivated.applied { 257 sqlx::query!( 258 "UPDATE users SET deactivated_at = NOW() WHERE did = $1", 259 did ··· 286 .into_response(); 287 } 288 if let Some(takedown) = &input.takedown { 289 + let status = if takedown.applied { 290 Some("takendown") 291 } else { 292 None ··· 294 if let Err(e) = crate::api::repo::record::sequence_account_event( 295 &state, 296 did, 297 + !takedown.applied, 298 status, 299 ) 300 .await ··· 303 } 304 } 305 if let Some(deactivated) = &input.deactivated { 306 + let status = if deactivated.applied { 307 Some("deactivated") 308 } else { 309 None ··· 311 if let Err(e) = crate::api::repo::record::sequence_account_event( 312 &state, 313 did, 314 + !deactivated.applied, 315 status, 316 ) 317 .await ··· 331 Json(json!({ 332 "subject": input.subject, 333 "takedown": input.takedown.as_ref().map(|t| json!({ 334 + "applied": t.applied, 335 "ref": t.r#ref 336 })), 337 "deactivated": input.deactivated.as_ref().map(|d| json!({ 338 + "applied": d.applied 339 })) 340 })), 341 ) ··· 346 let uri = input.subject.get("uri").and_then(|u| u.as_str()); 347 if let Some(uri) = uri { 348 if let Some(takedown) = &input.takedown { 349 + let takedown_ref = if takedown.applied { 350 takedown.r#ref.clone() 351 } else { 352 None ··· 375 Json(json!({ 376 "subject": input.subject, 377 "takedown": input.takedown.as_ref().map(|t| json!({ 378 + "applied": t.applied, 379 "ref": t.r#ref 380 })) 381 })), ··· 387 let cid = input.subject.get("cid").and_then(|c| c.as_str()); 388 if let Some(cid) = cid { 389 if let Some(takedown) = &input.takedown { 390 + let takedown_ref = if takedown.applied { 391 takedown.r#ref.clone() 392 } else { 393 None ··· 413 Json(json!({ 414 "subject": input.subject, 415 "takedown": input.takedown.as_ref().map(|t| json!({ 416 + "applied": t.applied, 417 "ref": t.r#ref 418 })) 419 })),
+1 -1
src/api/identity/did.rs
··· 780 } 781 } 782 783 - async fn update_plc_handle( 784 state: &AppState, 785 did: &str, 786 new_handle: &str,
··· 780 } 781 } 782 783 + pub async fn update_plc_handle( 784 state: &AppState, 785 did: &str, 786 new_handle: &str,
+10 -20
tests/account_notifications.rs
··· 1 mod common; 2 - use common::{base_url, client, create_account_and_login, get_db_connection_string}; 3 use serde_json::{Value, json}; 4 - use sqlx::PgPool; 5 use tranquil_pds::comms::{CommsType, NewComms, enqueue_comms}; 6 7 - async fn get_pool() -> PgPool { 8 - let conn_str = get_db_connection_string().await; 9 - sqlx::postgres::PgPoolOptions::new() 10 - .max_connections(5) 11 - .connect(&conn_str) 12 - .await 13 - .expect("Failed to connect to test database") 14 - } 15 - 16 #[tokio::test] 17 async fn test_get_notification_history() { 18 let client = client(); 19 let base = base_url().await; 20 - let pool = get_pool().await; 21 let (token, did) = create_account_and_login(&client).await; 22 23 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 24 - .fetch_one(&pool) 25 .await 26 .expect("User not found"); 27 ··· 33 format!("Subject {}", i), 34 format!("Body {}", i), 35 ); 36 - enqueue_comms(&pool, comms) 37 .await 38 .expect("Failed to enqueue"); 39 } ··· 86 .contains(&json!("discord")) 87 ); 88 89 - let pool = get_pool().await; 90 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 91 - .fetch_one(&pool) 92 .await 93 .expect("User not found"); 94 ··· 96 "SELECT body, metadata FROM comms_queue WHERE user_id = $1 AND comms_type = 'channel_verification' ORDER BY created_at DESC LIMIT 1", 97 user_id 98 ) 99 - .fetch_one(&pool) 100 .await 101 .expect("Verification code not found"); 102 ··· 213 async fn test_update_email_via_notification_prefs() { 214 let client = client(); 215 let base = base_url().await; 216 - let pool = get_pool().await; 217 let (token, did) = create_account_and_login(&client).await; 218 219 let unique_email = format!("newemail_{}@example.com", uuid::Uuid::new_v4()); ··· 240 ); 241 242 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 243 - .fetch_one(&pool) 244 .await 245 .expect("User not found"); 246 ··· 248 "SELECT body FROM comms_queue WHERE user_id = $1 AND comms_type = 'email_update' ORDER BY created_at DESC LIMIT 1", 249 user_id 250 ) 251 - .fetch_one(&pool) 252 .await 253 .expect("Verification code not found"); 254
··· 1 mod common; 2 + use common::{base_url, client, create_account_and_login, get_test_db_pool}; 3 use serde_json::{Value, json}; 4 use tranquil_pds::comms::{CommsType, NewComms, enqueue_comms}; 5 6 #[tokio::test] 7 async fn test_get_notification_history() { 8 let client = client(); 9 let base = base_url().await; 10 + let pool = get_test_db_pool().await; 11 let (token, did) = create_account_and_login(&client).await; 12 13 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 14 + .fetch_one(pool) 15 .await 16 .expect("User not found"); 17 ··· 23 format!("Subject {}", i), 24 format!("Body {}", i), 25 ); 26 + enqueue_comms(pool, comms) 27 .await 28 .expect("Failed to enqueue"); 29 } ··· 76 .contains(&json!("discord")) 77 ); 78 79 + let pool = get_test_db_pool().await; 80 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 81 + .fetch_one(pool) 82 .await 83 .expect("User not found"); 84 ··· 86 "SELECT body, metadata FROM comms_queue WHERE user_id = $1 AND comms_type = 'channel_verification' ORDER BY created_at DESC LIMIT 1", 87 user_id 88 ) 89 + .fetch_one(pool) 90 .await 91 .expect("Verification code not found"); 92 ··· 203 async fn test_update_email_via_notification_prefs() { 204 let client = client(); 205 let base = base_url().await; 206 + let pool = get_test_db_pool().await; 207 let (token, did) = create_account_and_login(&client).await; 208 209 let unique_email = format!("newemail_{}@example.com", uuid::Uuid::new_v4()); ··· 230 ); 231 232 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 233 + .fetch_one(pool) 234 .await 235 .expect("User not found"); 236 ··· 238 "SELECT body FROM comms_queue WHERE user_id = $1 AND comms_type = 'email_update' ORDER BY created_at DESC LIMIT 1", 239 user_id 240 ) 241 + .fetch_one(pool) 242 .await 243 .expect("Verification code not found"); 244
+6 -16
tests/admin_email.rs
··· 2 3 use reqwest::StatusCode; 4 use serde_json::{Value, json}; 5 - use sqlx::PgPool; 6 - 7 - async fn get_pool() -> PgPool { 8 - let conn_str = common::get_db_connection_string().await; 9 - sqlx::postgres::PgPoolOptions::new() 10 - .max_connections(5) 11 - .connect(&conn_str) 12 - .await 13 - .expect("Failed to connect to test database") 14 - } 15 16 #[tokio::test] 17 async fn test_send_email_success() { 18 let client = common::client(); 19 let base_url = common::base_url().await; 20 - let pool = get_pool().await; 21 let (access_jwt, did) = common::create_admin_account_and_login(&client).await; 22 let res = client 23 .post(format!("{}/xrpc/com.atproto.admin.sendEmail", base_url)) ··· 35 let body: Value = res.json().await.expect("Invalid JSON"); 36 assert_eq!(body["sent"], true); 37 let user = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 38 - .fetch_one(&pool) 39 .await 40 .expect("User not found"); 41 let notification = sqlx::query!( 42 "SELECT subject, body, comms_type as \"comms_type: String\" FROM comms_queue WHERE user_id = $1 AND comms_type = 'admin_email' ORDER BY created_at DESC LIMIT 1", 43 user.id 44 ) 45 - .fetch_one(&pool) 46 .await 47 .expect("Notification not found"); 48 assert_eq!(notification.subject.as_deref(), Some("Test Admin Email")); ··· 57 async fn test_send_email_default_subject() { 58 let client = common::client(); 59 let base_url = common::base_url().await; 60 - let pool = get_pool().await; 61 let (access_jwt, did) = common::create_admin_account_and_login(&client).await; 62 let res = client 63 .post(format!("{}/xrpc/com.atproto.admin.sendEmail", base_url)) ··· 74 let body: Value = res.json().await.expect("Invalid JSON"); 75 assert_eq!(body["sent"], true); 76 let user = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 77 - .fetch_one(&pool) 78 .await 79 .expect("User not found"); 80 let notification = sqlx::query!( 81 "SELECT subject FROM comms_queue WHERE user_id = $1 AND comms_type = 'admin_email' AND body = 'Email without subject' LIMIT 1", 82 user.id 83 ) 84 - .fetch_one(&pool) 85 .await 86 .expect("Notification not found"); 87 assert!(notification.subject.is_some());
··· 2 3 use reqwest::StatusCode; 4 use serde_json::{Value, json}; 5 6 #[tokio::test] 7 async fn test_send_email_success() { 8 let client = common::client(); 9 let base_url = common::base_url().await; 10 + let pool = common::get_test_db_pool().await; 11 let (access_jwt, did) = common::create_admin_account_and_login(&client).await; 12 let res = client 13 .post(format!("{}/xrpc/com.atproto.admin.sendEmail", base_url)) ··· 25 let body: Value = res.json().await.expect("Invalid JSON"); 26 assert_eq!(body["sent"], true); 27 let user = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 28 + .fetch_one(pool) 29 .await 30 .expect("User not found"); 31 let notification = sqlx::query!( 32 "SELECT subject, body, comms_type as \"comms_type: String\" FROM comms_queue WHERE user_id = $1 AND comms_type = 'admin_email' ORDER BY created_at DESC LIMIT 1", 33 user.id 34 ) 35 + .fetch_one(pool) 36 .await 37 .expect("Notification not found"); 38 assert_eq!(notification.subject.as_deref(), Some("Test Admin Email")); ··· 47 async fn test_send_email_default_subject() { 48 let client = common::client(); 49 let base_url = common::base_url().await; 50 + let pool = common::get_test_db_pool().await; 51 let (access_jwt, did) = common::create_admin_account_and_login(&client).await; 52 let res = client 53 .post(format!("{}/xrpc/com.atproto.admin.sendEmail", base_url)) ··· 64 let body: Value = res.json().await.expect("Invalid JSON"); 65 assert_eq!(body["sent"], true); 66 let user = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 67 + .fetch_one(pool) 68 .await 69 .expect("User not found"); 70 let notification = sqlx::query!( 71 "SELECT subject FROM comms_queue WHERE user_id = $1 AND comms_type = 'admin_email' AND body = 'Email without subject' LIMIT 1", 72 user.id 73 ) 74 + .fetch_one(pool) 75 .await 76 .expect("Notification not found"); 77 assert!(notification.subject.is_some());
+6 -6
tests/admin_moderation.rs
··· 88 "did": target_did 89 }, 90 "takedown": { 91 - "apply": true, 92 "ref": "mod-action-123" 93 } 94 }); ··· 134 "did": target_did 135 }, 136 "takedown": { 137 - "apply": true, 138 "ref": "mod-action-456" 139 } 140 }); ··· 153 "did": target_did 154 }, 155 "takedown": { 156 - "apply": false 157 } 158 }); 159 let res = client ··· 197 "did": target_did 198 }, 199 "deactivated": { 200 - "apply": true 201 } 202 }); 203 let res = client ··· 236 "did": "did:plc:test" 237 }, 238 "takedown": { 239 - "apply": true 240 } 241 }); 242 let res = client ··· 263 "did": "did:plc:test" 264 }, 265 "takedown": { 266 - "apply": true 267 } 268 }); 269 let res = client
··· 88 "did": target_did 89 }, 90 "takedown": { 91 + "applied": true, 92 "ref": "mod-action-123" 93 } 94 }); ··· 134 "did": target_did 135 }, 136 "takedown": { 137 + "applied": true, 138 "ref": "mod-action-456" 139 } 140 }); ··· 153 "did": target_did 154 }, 155 "takedown": { 156 + "applied": false 157 } 158 }); 159 let res = client ··· 197 "did": target_did 198 }, 199 "deactivated": { 200 + "applied": true 201 } 202 }); 203 let res = client ··· 236 "did": "did:plc:test" 237 }, 238 "takedown": { 239 + "applied": true 240 } 241 }); 242 let res = client ··· 263 "did": "did:plc:test" 264 }, 265 "takedown": { 266 + "applied": true 267 } 268 }); 269 let res = client
+21 -16
tests/common/mod.rs
··· 18 static SERVER_URL: OnceLock<String> = OnceLock::new(); 19 static APP_PORT: OnceLock<u16> = OnceLock::new(); 20 static MOCK_APPVIEW: OnceLock<MockServer> = OnceLock::new(); 21 22 #[cfg(not(feature = "external-infra"))] 23 use testcontainers::core::ContainerPort; ··· 237 async fn spawn_app(database_url: String) -> String { 238 use tranquil_pds::rate_limit::RateLimiters; 239 let pool = PgPoolOptions::new() 240 - .max_connections(50) 241 .connect(&database_url) 242 .await 243 .expect("Failed to connect to Postgres. Make sure the database is running."); ··· 245 .run(&pool) 246 .await 247 .expect("Failed to run migrations"); 248 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 249 let addr = listener.local_addr().unwrap(); 250 APP_PORT.set(addr.port()).ok(); ··· 292 } 293 294 #[allow(dead_code)] 295 pub async fn verify_new_account(client: &Client, did: &str) -> String { 296 - let conn_str = get_db_connection_string().await; 297 - let pool = sqlx::postgres::PgPoolOptions::new() 298 - .max_connections(2) 299 - .connect(&conn_str) 300 - .await 301 - .expect("Failed to connect to test database"); 302 let body_text: String = sqlx::query_scalar!( 303 "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", 304 did 305 ) 306 - .fetch_one(&pool) 307 .await 308 .expect("Failed to get verification code"); 309 ··· 454 if res.status() == StatusCode::OK { 455 let body: Value = res.json().await.expect("Invalid JSON"); 456 let did = body["did"].as_str().expect("No did").to_string(); 457 - let conn_str = get_db_connection_string().await; 458 - let pool = sqlx::postgres::PgPoolOptions::new() 459 - .max_connections(2) 460 - .connect(&conn_str) 461 - .await 462 - .expect("Failed to connect to test database"); 463 if make_admin { 464 sqlx::query!("UPDATE users SET is_admin = TRUE WHERE did = $1", &did) 465 - .execute(&pool) 466 .await 467 .expect("Failed to mark user as admin"); 468 } ··· 476 "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", 477 &did 478 ) 479 - .fetch_one(&pool) 480 .await 481 .expect("Failed to get verification from comms_queue"); 482 let lines: Vec<&str> = body_text.lines().collect();
··· 18 static SERVER_URL: OnceLock<String> = OnceLock::new(); 19 static APP_PORT: OnceLock<u16> = OnceLock::new(); 20 static MOCK_APPVIEW: OnceLock<MockServer> = OnceLock::new(); 21 + static TEST_DB_POOL: OnceLock<sqlx::PgPool> = OnceLock::new(); 22 23 #[cfg(not(feature = "external-infra"))] 24 use testcontainers::core::ContainerPort; ··· 238 async fn spawn_app(database_url: String) -> String { 239 use tranquil_pds::rate_limit::RateLimiters; 240 let pool = PgPoolOptions::new() 241 + .max_connections(3) 242 + .acquire_timeout(std::time::Duration::from_secs(30)) 243 .connect(&database_url) 244 .await 245 .expect("Failed to connect to Postgres. Make sure the database is running."); ··· 247 .run(&pool) 248 .await 249 .expect("Failed to run migrations"); 250 + let test_pool = PgPoolOptions::new() 251 + .max_connections(5) 252 + .acquire_timeout(std::time::Duration::from_secs(30)) 253 + .connect(&database_url) 254 + .await 255 + .expect("Failed to create test pool"); 256 + TEST_DB_POOL.set(test_pool).ok(); 257 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 258 let addr = listener.local_addr().unwrap(); 259 APP_PORT.set(addr.port()).ok(); ··· 301 } 302 303 #[allow(dead_code)] 304 + pub async fn get_test_db_pool() -> &'static sqlx::PgPool { 305 + base_url().await; 306 + TEST_DB_POOL.get().expect("TEST_DB_POOL not initialized") 307 + } 308 + 309 + #[allow(dead_code)] 310 pub async fn verify_new_account(client: &Client, did: &str) -> String { 311 + let pool = get_test_db_pool().await; 312 let body_text: String = sqlx::query_scalar!( 313 "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", 314 did 315 ) 316 + .fetch_one(pool) 317 .await 318 .expect("Failed to get verification code"); 319 ··· 464 if res.status() == StatusCode::OK { 465 let body: Value = res.json().await.expect("Invalid JSON"); 466 let did = body["did"].as_str().expect("No did").to_string(); 467 + let pool = get_test_db_pool().await; 468 if make_admin { 469 sqlx::query!("UPDATE users SET is_admin = TRUE WHERE did = $1", &did) 470 + .execute(pool) 471 .await 472 .expect("Failed to mark user as admin"); 473 } ··· 481 "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", 482 &did 483 ) 484 + .fetch_one(pool) 485 .await 486 .expect("Failed to get verification from comms_queue"); 487 let lines: Vec<&str> = body_text.lines().collect();
+13 -23
tests/delete_account.rs
··· 4 use common::*; 5 use reqwest::StatusCode; 6 use serde_json::{Value, json}; 7 - use sqlx::PgPool; 8 - 9 - async fn get_pool() -> PgPool { 10 - let conn_str = get_db_connection_string().await; 11 - sqlx::postgres::PgPoolOptions::new() 12 - .max_connections(5) 13 - .connect(&conn_str) 14 - .await 15 - .expect("Failed to connect to test database") 16 - } 17 18 async fn create_verified_account( 19 client: &reqwest::Client, ··· 61 .await 62 .expect("Failed to request account deletion"); 63 assert_eq!(request_delete_res.status(), StatusCode::OK); 64 - let pool = get_pool().await; 65 let row = sqlx::query!( 66 "SELECT token FROM account_deletion_requests WHERE did = $1", 67 did 68 ) 69 - .fetch_one(&pool) 70 .await 71 .expect("Failed to query deletion token"); 72 let token = row.token; ··· 86 .expect("Failed to delete account"); 87 assert_eq!(delete_res.status(), StatusCode::OK); 88 let user_row = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 89 - .fetch_optional(&pool) 90 .await 91 .expect("Failed to query user"); 92 assert!(user_row.is_none(), "User should be deleted from database"); ··· 118 .await 119 .expect("Failed to request account deletion"); 120 assert_eq!(request_delete_res.status(), StatusCode::OK); 121 - let pool = get_pool().await; 122 let row = sqlx::query!( 123 "SELECT token FROM account_deletion_requests WHERE did = $1", 124 did 125 ) 126 - .fetch_one(&pool) 127 .await 128 .expect("Failed to query deletion token"); 129 let token = row.token; ··· 208 .await 209 .expect("Failed to request account deletion"); 210 assert_eq!(request_delete_res.status(), StatusCode::OK); 211 - let pool = get_pool().await; 212 let row = sqlx::query!( 213 "SELECT token FROM account_deletion_requests WHERE did = $1", 214 did 215 ) 216 - .fetch_one(&pool) 217 .await 218 .expect("Failed to query deletion token"); 219 let token = row.token; ··· 221 "UPDATE account_deletion_requests SET expires_at = NOW() - INTERVAL '1 hour' WHERE token = $1", 222 token 223 ) 224 - .execute(&pool) 225 .await 226 .expect("Failed to expire token"); 227 let delete_payload = json!({ ··· 267 .await 268 .expect("Failed to request account deletion"); 269 assert_eq!(request_delete_res.status(), StatusCode::OK); 270 - let pool = get_pool().await; 271 let row = sqlx::query!( 272 "SELECT token FROM account_deletion_requests WHERE did = $1", 273 did1 274 ) 275 - .fetch_one(&pool) 276 .await 277 .expect("Failed to query deletion token"); 278 let token = row.token; ··· 328 .await 329 .expect("Failed to request account deletion"); 330 assert_eq!(request_delete_res.status(), StatusCode::OK); 331 - let pool = get_pool().await; 332 let row = sqlx::query!( 333 "SELECT token FROM account_deletion_requests WHERE did = $1", 334 did 335 ) 336 - .fetch_one(&pool) 337 .await 338 .expect("Failed to query deletion token"); 339 let token = row.token; ··· 353 .expect("Failed to delete account"); 354 assert_eq!(delete_res.status(), StatusCode::OK); 355 let user_row = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 356 - .fetch_optional(&pool) 357 .await 358 .expect("Failed to query user"); 359 assert!(user_row.is_none(), "User should be deleted from database");
··· 4 use common::*; 5 use reqwest::StatusCode; 6 use serde_json::{Value, json}; 7 8 async fn create_verified_account( 9 client: &reqwest::Client, ··· 51 .await 52 .expect("Failed to request account deletion"); 53 assert_eq!(request_delete_res.status(), StatusCode::OK); 54 + let pool = get_test_db_pool().await; 55 let row = sqlx::query!( 56 "SELECT token FROM account_deletion_requests WHERE did = $1", 57 did 58 ) 59 + .fetch_one(pool) 60 .await 61 .expect("Failed to query deletion token"); 62 let token = row.token; ··· 76 .expect("Failed to delete account"); 77 assert_eq!(delete_res.status(), StatusCode::OK); 78 let user_row = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 79 + .fetch_optional(pool) 80 .await 81 .expect("Failed to query user"); 82 assert!(user_row.is_none(), "User should be deleted from database"); ··· 108 .await 109 .expect("Failed to request account deletion"); 110 assert_eq!(request_delete_res.status(), StatusCode::OK); 111 + let pool = get_test_db_pool().await; 112 let row = sqlx::query!( 113 "SELECT token FROM account_deletion_requests WHERE did = $1", 114 did 115 ) 116 + .fetch_one(pool) 117 .await 118 .expect("Failed to query deletion token"); 119 let token = row.token; ··· 198 .await 199 .expect("Failed to request account deletion"); 200 assert_eq!(request_delete_res.status(), StatusCode::OK); 201 + let pool = get_test_db_pool().await; 202 let row = sqlx::query!( 203 "SELECT token FROM account_deletion_requests WHERE did = $1", 204 did 205 ) 206 + .fetch_one(pool) 207 .await 208 .expect("Failed to query deletion token"); 209 let token = row.token; ··· 211 "UPDATE account_deletion_requests SET expires_at = NOW() - INTERVAL '1 hour' WHERE token = $1", 212 token 213 ) 214 + .execute(pool) 215 .await 216 .expect("Failed to expire token"); 217 let delete_payload = json!({ ··· 257 .await 258 .expect("Failed to request account deletion"); 259 assert_eq!(request_delete_res.status(), StatusCode::OK); 260 + let pool = get_test_db_pool().await; 261 let row = sqlx::query!( 262 "SELECT token FROM account_deletion_requests WHERE did = $1", 263 did1 264 ) 265 + .fetch_one(pool) 266 .await 267 .expect("Failed to query deletion token"); 268 let token = row.token; ··· 318 .await 319 .expect("Failed to request account deletion"); 320 assert_eq!(request_delete_res.status(), StatusCode::OK); 321 + let pool = get_test_db_pool().await; 322 let row = sqlx::query!( 323 "SELECT token FROM account_deletion_requests WHERE did = $1", 324 did 325 ) 326 + .fetch_one(pool) 327 .await 328 .expect("Failed to query deletion token"); 329 let token = row.token; ··· 343 .expect("Failed to delete account"); 344 assert_eq!(delete_res.status(), StatusCode::OK); 345 let user_row = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 346 + .fetch_optional(pool) 347 .await 348 .expect("Failed to query user"); 349 assert!(user_row.is_none(), "User should be deleted from database");
+12 -21
tests/email_update.rs
··· 3 use serde_json::{Value, json}; 4 use sqlx::PgPool; 5 6 - async fn get_pool() -> PgPool { 7 - let conn_str = common::get_db_connection_string().await; 8 - sqlx::postgres::PgPoolOptions::new() 9 - .max_connections(5) 10 - .connect(&conn_str) 11 - .await 12 - .expect("Failed to connect to test database") 13 - } 14 - 15 async fn get_email_update_token(pool: &PgPool, did: &str) -> String { 16 let body_text: String = sqlx::query_scalar!( 17 "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_update' ORDER BY created_at DESC LIMIT 1", ··· 88 async fn test_update_email_flow_success() { 89 let client = common::client(); 90 let base_url = common::base_url().await; 91 - let pool = get_pool().await; 92 let handle = format!("emailup-{}", uuid::Uuid::new_v4()); 93 let email = format!("{}@example.com", handle); 94 let (access_jwt, did) = create_verified_account(&client, &base_url, &handle, &email).await; ··· 107 let body: Value = res.json().await.expect("Invalid JSON"); 108 assert_eq!(body["tokenRequired"], true); 109 110 - let code = get_email_update_token(&pool, &did).await; 111 112 let res = client 113 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) ··· 122 assert_eq!(res.status(), StatusCode::OK); 123 124 let user_email: Option<String> = sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did) 125 - .fetch_one(&pool) 126 .await 127 .expect("User not found"); 128 assert_eq!(user_email, Some(new_email)); ··· 244 async fn test_confirm_email_confirms_existing_email() { 245 let client = common::client(); 246 let base_url = common::base_url().await; 247 - let pool = get_pool().await; 248 let handle = format!("emailconfirm-{}", uuid::Uuid::new_v4()); 249 let email = format!("{}@example.com", handle); 250 ··· 270 "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", 271 did 272 ) 273 - .fetch_one(&pool) 274 .await 275 .expect("Verification email not found"); 276 ··· 296 "SELECT email_verified FROM users WHERE did = $1", 297 did 298 ) 299 - .fetch_one(&pool) 300 .await 301 .expect("User not found"); 302 assert!(verified); ··· 306 async fn test_confirm_email_rejects_wrong_email() { 307 let client = common::client(); 308 let base_url = common::base_url().await; 309 - let pool = get_pool().await; 310 let handle = format!("emailconf-wrong-{}", uuid::Uuid::new_v4()); 311 let email = format!("{}@example.com", handle); 312 ··· 332 "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", 333 did 334 ) 335 - .fetch_one(&pool) 336 .await 337 .expect("Verification email not found"); 338 ··· 400 async fn test_unverified_account_can_update_email_without_token() { 401 let client = common::client(); 402 let base_url = common::base_url().await; 403 - let pool = get_pool().await; 404 let handle = format!("emailup-unverified-{}", uuid::Uuid::new_v4()); 405 let email = format!("{}@example.com", handle); 406 ··· 454 455 let user_email: Option<String> = 456 sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did) 457 - .fetch_one(&pool) 458 .await 459 .expect("User not found"); 460 assert_eq!(user_email, Some(new_email)); ··· 464 async fn test_update_email_taken_by_another_user() { 465 let client = common::client(); 466 let base_url = common::base_url().await; 467 - let pool = get_pool().await; 468 469 let handle1 = format!("emailup-dup1-{}", uuid::Uuid::new_v4()); 470 let email1 = format!("{}@example.com", handle1); ··· 485 .expect("Failed to request email update"); 486 assert_eq!(res.status(), StatusCode::OK); 487 488 - let code = get_email_update_token(&pool, &did2).await; 489 490 let res = client 491 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
··· 3 use serde_json::{Value, json}; 4 use sqlx::PgPool; 5 6 async fn get_email_update_token(pool: &PgPool, did: &str) -> String { 7 let body_text: String = sqlx::query_scalar!( 8 "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_update' ORDER BY created_at DESC LIMIT 1", ··· 79 async fn test_update_email_flow_success() { 80 let client = common::client(); 81 let base_url = common::base_url().await; 82 + let pool = common::get_test_db_pool().await; 83 let handle = format!("emailup-{}", uuid::Uuid::new_v4()); 84 let email = format!("{}@example.com", handle); 85 let (access_jwt, did) = create_verified_account(&client, &base_url, &handle, &email).await; ··· 98 let body: Value = res.json().await.expect("Invalid JSON"); 99 assert_eq!(body["tokenRequired"], true); 100 101 + let code = get_email_update_token(pool, &did).await; 102 103 let res = client 104 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) ··· 113 assert_eq!(res.status(), StatusCode::OK); 114 115 let user_email: Option<String> = sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did) 116 + .fetch_one(pool) 117 .await 118 .expect("User not found"); 119 assert_eq!(user_email, Some(new_email)); ··· 235 async fn test_confirm_email_confirms_existing_email() { 236 let client = common::client(); 237 let base_url = common::base_url().await; 238 + let pool = common::get_test_db_pool().await; 239 let handle = format!("emailconfirm-{}", uuid::Uuid::new_v4()); 240 let email = format!("{}@example.com", handle); 241 ··· 261 "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", 262 did 263 ) 264 + .fetch_one(pool) 265 .await 266 .expect("Verification email not found"); 267 ··· 287 "SELECT email_verified FROM users WHERE did = $1", 288 did 289 ) 290 + .fetch_one(pool) 291 .await 292 .expect("User not found"); 293 assert!(verified); ··· 297 async fn test_confirm_email_rejects_wrong_email() { 298 let client = common::client(); 299 let base_url = common::base_url().await; 300 + let pool = common::get_test_db_pool().await; 301 let handle = format!("emailconf-wrong-{}", uuid::Uuid::new_v4()); 302 let email = format!("{}@example.com", handle); 303 ··· 323 "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", 324 did 325 ) 326 + .fetch_one(pool) 327 .await 328 .expect("Verification email not found"); 329 ··· 391 async fn test_unverified_account_can_update_email_without_token() { 392 let client = common::client(); 393 let base_url = common::base_url().await; 394 + let pool = common::get_test_db_pool().await; 395 let handle = format!("emailup-unverified-{}", uuid::Uuid::new_v4()); 396 let email = format!("{}@example.com", handle); 397 ··· 445 446 let user_email: Option<String> = 447 sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did) 448 + .fetch_one(pool) 449 .await 450 .expect("User not found"); 451 assert_eq!(user_email, Some(new_email)); ··· 455 async fn test_update_email_taken_by_another_user() { 456 let client = common::client(); 457 let base_url = common::base_url().await; 458 + let pool = common::get_test_db_pool().await; 459 460 let handle1 = format!("emailup-dup1-{}", uuid::Uuid::new_v4()); 461 let email1 = format!("{}@example.com", handle1); ··· 476 .expect("Failed to request email update"); 477 assert_eq!(res.status(), StatusCode::OK); 478 479 + let code = get_email_update_token(pool, &did2).await; 480 481 let res = client 482 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
+4 -14
tests/helpers/mod.rs
··· 217 218 #[allow(dead_code)] 219 pub async fn set_account_takedown(did: &str, takedown_ref: Option<&str>) { 220 - let conn_str = get_db_connection_string().await; 221 - let pool = sqlx::postgres::PgPoolOptions::new() 222 - .max_connections(2) 223 - .connect(&conn_str) 224 - .await 225 - .expect("Failed to connect to test database"); 226 sqlx::query!( 227 "UPDATE users SET takedown_ref = $1 WHERE did = $2", 228 takedown_ref, 229 did 230 ) 231 - .execute(&pool) 232 .await 233 .expect("Failed to update takedown_ref"); 234 } 235 236 #[allow(dead_code)] 237 pub async fn set_account_deactivated(did: &str, deactivated: bool) { 238 - let conn_str = get_db_connection_string().await; 239 - let pool = sqlx::postgres::PgPoolOptions::new() 240 - .max_connections(2) 241 - .connect(&conn_str) 242 - .await 243 - .expect("Failed to connect to test database"); 244 let deactivated_at: Option<chrono::DateTime<Utc>> = 245 if deactivated { Some(Utc::now()) } else { None }; 246 sqlx::query!( ··· 248 deactivated_at, 249 did 250 ) 251 - .execute(&pool) 252 .await 253 .expect("Failed to update deactivated_at"); 254 }
··· 217 218 #[allow(dead_code)] 219 pub async fn set_account_takedown(did: &str, takedown_ref: Option<&str>) { 220 + let pool = get_test_db_pool().await; 221 sqlx::query!( 222 "UPDATE users SET takedown_ref = $1 WHERE did = $2", 223 takedown_ref, 224 did 225 ) 226 + .execute(pool) 227 .await 228 .expect("Failed to update takedown_ref"); 229 } 230 231 #[allow(dead_code)] 232 pub async fn set_account_deactivated(did: &str, deactivated: bool) { 233 + let pool = get_test_db_pool().await; 234 let deactivated_at: Option<chrono::DateTime<Utc>> = 235 if deactivated { Some(Utc::now()) } else { None }; 236 sqlx::query!( ··· 238 deactivated_at, 239 did 240 ) 241 + .execute(pool) 242 .await 243 .expect("Failed to update deactivated_at"); 244 }
+3 -7
tests/jwt_security.rs
··· 2 mod common; 3 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 4 use chrono::{Duration, Utc}; 5 - use common::{base_url, client, create_account_and_login, get_db_connection_string}; 6 use k256::SecretKey; 7 use k256::ecdsa::{Signature, SigningKey, signature::Signer}; 8 use rand::rngs::OsRng; ··· 683 let account: Value = create_res.json().await.unwrap(); 684 let did = account["did"].as_str().unwrap(); 685 686 - let pool = sqlx::postgres::PgPoolOptions::new() 687 - .max_connections(2) 688 - .connect(&get_db_connection_string().await) 689 - .await 690 - .unwrap(); 691 let body_text: String = sqlx::query_scalar!( 692 "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", 693 did 694 - ).fetch_one(&pool).await.unwrap(); 695 let lines: Vec<&str> = body_text.lines().collect(); 696 let code = lines 697 .iter()
··· 2 mod common; 3 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 4 use chrono::{Duration, Utc}; 5 + use common::{base_url, client, create_account_and_login, get_test_db_pool}; 6 use k256::SecretKey; 7 use k256::ecdsa::{Signature, SigningKey, signature::Signer}; 8 use rand::rngs::OsRng; ··· 683 let account: Value = create_res.json().await.unwrap(); 684 let did = account["did"].as_str().unwrap(); 685 686 + let pool = get_test_db_pool().await; 687 let body_text: String = sqlx::query_scalar!( 688 "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", 689 did 690 + ).fetch_one(pool).await.unwrap(); 691 let lines: Vec<&str> = body_text.lines().collect(); 692 let code = lines 693 .iter()
+13 -23
tests/notifications.rs
··· 1 mod common; 2 - use sqlx::PgPool; 3 use tranquil_pds::comms::{ 4 CommsChannel, CommsStatus, CommsType, NewComms, enqueue_comms, enqueue_welcome, 5 }; 6 7 - async fn get_pool() -> PgPool { 8 - let conn_str = common::get_db_connection_string().await; 9 - sqlx::postgres::PgPoolOptions::new() 10 - .max_connections(5) 11 - .connect(&conn_str) 12 - .await 13 - .expect("Failed to connect to test database") 14 - } 15 - 16 #[tokio::test] 17 async fn test_enqueue_comms() { 18 - let pool = get_pool().await; 19 let (_, did) = common::create_account_and_login(&common::client()).await; 20 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 21 - .fetch_one(&pool) 22 .await 23 .expect("User not found"); 24 let item = NewComms::email( ··· 28 "Test Subject".to_string(), 29 "Test body".to_string(), 30 ); 31 - let comms_id = enqueue_comms(&pool, item) 32 .await 33 .expect("Failed to enqueue comms"); 34 let row = sqlx::query!( ··· 43 "#, 44 comms_id 45 ) 46 - .fetch_one(&pool) 47 .await 48 .expect("Comms not found"); 49 assert_eq!(row.user_id, user_id); ··· 57 58 #[tokio::test] 59 async fn test_enqueue_welcome() { 60 - let pool = get_pool().await; 61 let (_, did) = common::create_account_and_login(&common::client()).await; 62 let user_row = sqlx::query!("SELECT id, email, handle FROM users WHERE did = $1", did) 63 - .fetch_one(&pool) 64 .await 65 .expect("User not found"); 66 - let comms_id = enqueue_welcome(&pool, user_row.id, "example.com") 67 .await 68 .expect("Failed to enqueue welcome comms"); 69 let row = sqlx::query!( ··· 76 "#, 77 comms_id 78 ) 79 - .fetch_one(&pool) 80 .await 81 .expect("Comms not found"); 82 assert_eq!(Some(row.recipient), user_row.email); ··· 87 88 #[tokio::test] 89 async fn test_comms_queue_status_index() { 90 - let pool = get_pool().await; 91 let (_, did) = common::create_account_and_login(&common::client()).await; 92 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 93 - .fetch_one(&pool) 94 .await 95 .expect("User not found"); 96 let initial_count: i64 = sqlx::query_scalar!( 97 "SELECT COUNT(*) FROM comms_queue WHERE status = 'pending' AND user_id = $1", 98 user_id 99 ) 100 - .fetch_one(&pool) 101 .await 102 .expect("Failed to count") 103 .unwrap_or(0); ··· 109 "Test".to_string(), 110 "Body".to_string(), 111 ); 112 - enqueue_comms(&pool, item).await.expect("Failed to enqueue"); 113 } 114 let final_count: i64 = sqlx::query_scalar!( 115 "SELECT COUNT(*) FROM comms_queue WHERE status = 'pending' AND user_id = $1", 116 user_id 117 ) 118 - .fetch_one(&pool) 119 .await 120 .expect("Failed to count") 121 .unwrap_or(0);
··· 1 mod common; 2 use tranquil_pds::comms::{ 3 CommsChannel, CommsStatus, CommsType, NewComms, enqueue_comms, enqueue_welcome, 4 }; 5 6 #[tokio::test] 7 async fn test_enqueue_comms() { 8 + let pool = common::get_test_db_pool().await; 9 let (_, did) = common::create_account_and_login(&common::client()).await; 10 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 11 + .fetch_one(pool) 12 .await 13 .expect("User not found"); 14 let item = NewComms::email( ··· 18 "Test Subject".to_string(), 19 "Test body".to_string(), 20 ); 21 + let comms_id = enqueue_comms(pool, item) 22 .await 23 .expect("Failed to enqueue comms"); 24 let row = sqlx::query!( ··· 33 "#, 34 comms_id 35 ) 36 + .fetch_one(pool) 37 .await 38 .expect("Comms not found"); 39 assert_eq!(row.user_id, user_id); ··· 47 48 #[tokio::test] 49 async fn test_enqueue_welcome() { 50 + let pool = common::get_test_db_pool().await; 51 let (_, did) = common::create_account_and_login(&common::client()).await; 52 let user_row = sqlx::query!("SELECT id, email, handle FROM users WHERE did = $1", did) 53 + .fetch_one(pool) 54 .await 55 .expect("User not found"); 56 + let comms_id = enqueue_welcome(pool, user_row.id, "example.com") 57 .await 58 .expect("Failed to enqueue welcome comms"); 59 let row = sqlx::query!( ··· 66 "#, 67 comms_id 68 ) 69 + .fetch_one(pool) 70 .await 71 .expect("Comms not found"); 72 assert_eq!(Some(row.recipient), user_row.email); ··· 77 78 #[tokio::test] 79 async fn test_comms_queue_status_index() { 80 + let pool = common::get_test_db_pool().await; 81 let (_, did) = common::create_account_and_login(&common::client()).await; 82 let user_id: uuid::Uuid = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 83 + .fetch_one(pool) 84 .await 85 .expect("User not found"); 86 let initial_count: i64 = sqlx::query_scalar!( 87 "SELECT COUNT(*) FROM comms_queue WHERE status = 'pending' AND user_id = $1", 88 user_id 89 ) 90 + .fetch_one(pool) 91 .await 92 .expect("Failed to count") 93 .unwrap_or(0); ··· 99 "Test".to_string(), 100 "Body".to_string(), 101 ); 102 + enqueue_comms(pool, item).await.expect("Failed to enqueue"); 103 } 104 let final_count: i64 = sqlx::query_scalar!( 105 "SELECT COUNT(*) FROM comms_queue WHERE status = 'pending' AND user_id = $1", 106 user_id 107 ) 108 + .fetch_one(pool) 109 .await 110 .expect("Failed to count") 111 .unwrap_or(0);
+9 -24
tests/oauth.rs
··· 2 mod helpers; 3 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 4 use chrono::Utc; 5 - use common::{base_url, client, get_db_connection_string}; 6 use helpers::verify_new_account; 7 use reqwest::{StatusCode, redirect}; 8 use serde_json::{Value, json}; ··· 449 let account: Value = create_res.json().await.unwrap(); 450 let user_did = account["did"].as_str().unwrap(); 451 verify_new_account(&http_client, user_did).await; 452 - let db_url = get_db_connection_string().await; 453 - let pool = sqlx::postgres::PgPoolOptions::new() 454 - .max_connections(1) 455 - .connect(&db_url) 456 - .await 457 - .unwrap(); 458 sqlx::query("UPDATE users SET two_factor_enabled = true WHERE did = $1") 459 .bind(user_did) 460 - .execute(&pool) 461 .await 462 .unwrap(); 463 let redirect_uri = "https://example.com/2fa-callback"; ··· 516 let twofa_code: String = 517 sqlx::query_scalar("SELECT code FROM oauth_2fa_challenge WHERE request_uri = $1") 518 .bind(request_uri) 519 - .fetch_one(&pool) 520 .await 521 .unwrap(); 522 let twofa_res = http_client ··· 575 let account: Value = create_res.json().await.unwrap(); 576 let user_did = account["did"].as_str().unwrap(); 577 verify_new_account(&http_client, user_did).await; 578 - let db_url = get_db_connection_string().await; 579 - let pool = sqlx::postgres::PgPoolOptions::new() 580 - .max_connections(1) 581 - .connect(&db_url) 582 - .await 583 - .unwrap(); 584 sqlx::query("UPDATE users SET two_factor_enabled = true WHERE did = $1") 585 .bind(user_did) 586 - .execute(&pool) 587 .await 588 .unwrap(); 589 let redirect_uri = "https://example.com/2fa-lockout-callback"; ··· 754 .json::<Value>() 755 .await 756 .unwrap(); 757 - let db_url = get_db_connection_string().await; 758 - let pool = sqlx::postgres::PgPoolOptions::new() 759 - .max_connections(1) 760 - .connect(&db_url) 761 - .await 762 - .unwrap(); 763 sqlx::query("UPDATE users SET two_factor_enabled = true WHERE did = $1") 764 .bind(&user_did) 765 - .execute(&pool) 766 .await 767 .unwrap(); 768 let (code_verifier2, code_challenge2) = generate_pkce(); ··· 803 let twofa_code: String = 804 sqlx::query_scalar("SELECT code FROM oauth_2fa_challenge WHERE request_uri = $1") 805 .bind(request_uri2) 806 - .fetch_one(&pool) 807 .await 808 .unwrap(); 809 let twofa_res = http_client
··· 2 mod helpers; 3 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 4 use chrono::Utc; 5 + use common::{base_url, client, get_test_db_pool}; 6 use helpers::verify_new_account; 7 use reqwest::{StatusCode, redirect}; 8 use serde_json::{Value, json}; ··· 449 let account: Value = create_res.json().await.unwrap(); 450 let user_did = account["did"].as_str().unwrap(); 451 verify_new_account(&http_client, user_did).await; 452 + let pool = get_test_db_pool().await; 453 sqlx::query("UPDATE users SET two_factor_enabled = true WHERE did = $1") 454 .bind(user_did) 455 + .execute(pool) 456 .await 457 .unwrap(); 458 let redirect_uri = "https://example.com/2fa-callback"; ··· 511 let twofa_code: String = 512 sqlx::query_scalar("SELECT code FROM oauth_2fa_challenge WHERE request_uri = $1") 513 .bind(request_uri) 514 + .fetch_one(pool) 515 .await 516 .unwrap(); 517 let twofa_res = http_client ··· 570 let account: Value = create_res.json().await.unwrap(); 571 let user_did = account["did"].as_str().unwrap(); 572 verify_new_account(&http_client, user_did).await; 573 + let pool = get_test_db_pool().await; 574 sqlx::query("UPDATE users SET two_factor_enabled = true WHERE did = $1") 575 .bind(user_did) 576 + .execute(pool) 577 .await 578 .unwrap(); 579 let redirect_uri = "https://example.com/2fa-lockout-callback"; ··· 744 .json::<Value>() 745 .await 746 .unwrap(); 747 + let pool = get_test_db_pool().await; 748 sqlx::query("UPDATE users SET two_factor_enabled = true WHERE did = $1") 749 .bind(&user_did) 750 + .execute(pool) 751 .await 752 .unwrap(); 753 let (code_verifier2, code_challenge2) = generate_pkce(); ··· 788 let twofa_code: String = 789 sqlx::query_scalar("SELECT code FROM oauth_2fa_challenge WHERE request_uri = $1") 790 .bind(request_uri2) 791 + .fetch_one(pool) 792 .await 793 .unwrap(); 794 let twofa_res = http_client
+14 -24
tests/password_reset.rs
··· 3 use helpers::verify_new_account; 4 use reqwest::StatusCode; 5 use serde_json::{Value, json}; 6 - use sqlx::PgPool; 7 - 8 - async fn get_pool() -> PgPool { 9 - let conn_str = common::get_db_connection_string().await; 10 - sqlx::postgres::PgPoolOptions::new() 11 - .max_connections(5) 12 - .connect(&conn_str) 13 - .await 14 - .expect("Failed to connect to test database") 15 - } 16 17 #[tokio::test] 18 async fn test_request_password_reset_creates_code() { 19 let client = common::client(); 20 let base_url = common::base_url().await; 21 - let pool = get_pool().await; 22 let handle = format!("pwreset-{}", uuid::Uuid::new_v4()); 23 let email = format!("{}@example.com", handle); 24 let payload = json!({ ··· 50 "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1", 51 email 52 ) 53 - .fetch_one(&pool) 54 .await 55 .expect("User not found"); 56 assert!(user.password_reset_code.is_some()); ··· 80 async fn test_reset_password_with_valid_token() { 81 let client = common::client(); 82 let base_url = common::base_url().await; 83 - let pool = get_pool().await; 84 let handle = format!("pwreset2-{}", uuid::Uuid::new_v4()); 85 let email = format!("{}@example.com", handle); 86 let old_password = "Oldpass123!"; ··· 117 "SELECT password_reset_code FROM users WHERE email = $1", 118 email 119 ) 120 - .fetch_one(&pool) 121 .await 122 .expect("User not found"); 123 let token = user.password_reset_code.expect("No reset code"); ··· 138 "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1", 139 email 140 ) 141 - .fetch_one(&pool) 142 .await 143 .expect("User not found"); 144 assert!(user.password_reset_code.is_none()); ··· 196 async fn test_reset_password_with_expired_token() { 197 let client = common::client(); 198 let base_url = common::base_url().await; 199 - let pool = get_pool().await; 200 let handle = format!("pwreset3-{}", uuid::Uuid::new_v4()); 201 let email = format!("{}@example.com", handle); 202 let payload = json!({ ··· 228 "SELECT password_reset_code FROM users WHERE email = $1", 229 email 230 ) 231 - .fetch_one(&pool) 232 .await 233 .expect("User not found"); 234 let token = user.password_reset_code.expect("No reset code"); ··· 236 "UPDATE users SET password_reset_code_expires_at = NOW() - INTERVAL '1 hour' WHERE email = $1", 237 email 238 ) 239 - .execute(&pool) 240 .await 241 .expect("Failed to expire token"); 242 let res = client ··· 260 async fn test_reset_password_invalidates_sessions() { 261 let client = common::client(); 262 let base_url = common::base_url().await; 263 - let pool = get_pool().await; 264 let handle = format!("pwreset4-{}", uuid::Uuid::new_v4()); 265 let email = format!("{}@example.com", handle); 266 let payload = json!({ ··· 302 "SELECT password_reset_code FROM users WHERE email = $1", 303 email 304 ) 305 - .fetch_one(&pool) 306 .await 307 .expect("User not found"); 308 let token = user.password_reset_code.expect("No reset code"); ··· 348 349 #[tokio::test] 350 async fn test_reset_password_creates_notification() { 351 - let pool = get_pool().await; 352 let client = common::client(); 353 let base_url = common::base_url().await; 354 let handle = format!("pwreset5-{}", uuid::Uuid::new_v4()); ··· 369 .expect("Failed to create account"); 370 assert_eq!(res.status(), StatusCode::OK); 371 let user = sqlx::query!("SELECT id FROM users WHERE email = $1", email) 372 - .fetch_one(&pool) 373 .await 374 .expect("User not found"); 375 let initial_count: i64 = sqlx::query_scalar!( 376 "SELECT COUNT(*) FROM comms_queue WHERE user_id = $1 AND comms_type = 'password_reset'", 377 user.id 378 ) 379 - .fetch_one(&pool) 380 .await 381 .expect("Failed to count") 382 .unwrap_or(0); ··· 394 "SELECT COUNT(*) FROM comms_queue WHERE user_id = $1 AND comms_type = 'password_reset'", 395 user.id 396 ) 397 - .fetch_one(&pool) 398 .await 399 .expect("Failed to count") 400 .unwrap_or(0);
··· 3 use helpers::verify_new_account; 4 use reqwest::StatusCode; 5 use serde_json::{Value, json}; 6 7 #[tokio::test] 8 async fn test_request_password_reset_creates_code() { 9 let client = common::client(); 10 let base_url = common::base_url().await; 11 + let pool = common::get_test_db_pool().await; 12 let handle = format!("pwreset-{}", uuid::Uuid::new_v4()); 13 let email = format!("{}@example.com", handle); 14 let payload = json!({ ··· 40 "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1", 41 email 42 ) 43 + .fetch_one(pool) 44 .await 45 .expect("User not found"); 46 assert!(user.password_reset_code.is_some()); ··· 70 async fn test_reset_password_with_valid_token() { 71 let client = common::client(); 72 let base_url = common::base_url().await; 73 + let pool = common::get_test_db_pool().await; 74 let handle = format!("pwreset2-{}", uuid::Uuid::new_v4()); 75 let email = format!("{}@example.com", handle); 76 let old_password = "Oldpass123!"; ··· 107 "SELECT password_reset_code FROM users WHERE email = $1", 108 email 109 ) 110 + .fetch_one(pool) 111 .await 112 .expect("User not found"); 113 let token = user.password_reset_code.expect("No reset code"); ··· 128 "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1", 129 email 130 ) 131 + .fetch_one(pool) 132 .await 133 .expect("User not found"); 134 assert!(user.password_reset_code.is_none()); ··· 186 async fn test_reset_password_with_expired_token() { 187 let client = common::client(); 188 let base_url = common::base_url().await; 189 + let pool = common::get_test_db_pool().await; 190 let handle = format!("pwreset3-{}", uuid::Uuid::new_v4()); 191 let email = format!("{}@example.com", handle); 192 let payload = json!({ ··· 218 "SELECT password_reset_code FROM users WHERE email = $1", 219 email 220 ) 221 + .fetch_one(pool) 222 .await 223 .expect("User not found"); 224 let token = user.password_reset_code.expect("No reset code"); ··· 226 "UPDATE users SET password_reset_code_expires_at = NOW() - INTERVAL '1 hour' WHERE email = $1", 227 email 228 ) 229 + .execute(pool) 230 .await 231 .expect("Failed to expire token"); 232 let res = client ··· 250 async fn test_reset_password_invalidates_sessions() { 251 let client = common::client(); 252 let base_url = common::base_url().await; 253 + let pool = common::get_test_db_pool().await; 254 let handle = format!("pwreset4-{}", uuid::Uuid::new_v4()); 255 let email = format!("{}@example.com", handle); 256 let payload = json!({ ··· 292 "SELECT password_reset_code FROM users WHERE email = $1", 293 email 294 ) 295 + .fetch_one(pool) 296 .await 297 .expect("User not found"); 298 let token = user.password_reset_code.expect("No reset code"); ··· 338 339 #[tokio::test] 340 async fn test_reset_password_creates_notification() { 341 + let pool = common::get_test_db_pool().await; 342 let client = common::client(); 343 let base_url = common::base_url().await; 344 let handle = format!("pwreset5-{}", uuid::Uuid::new_v4()); ··· 359 .expect("Failed to create account"); 360 assert_eq!(res.status(), StatusCode::OK); 361 let user = sqlx::query!("SELECT id FROM users WHERE email = $1", email) 362 + .fetch_one(pool) 363 .await 364 .expect("User not found"); 365 let initial_count: i64 = sqlx::query_scalar!( 366 "SELECT COUNT(*) FROM comms_queue WHERE user_id = $1 AND comms_type = 'password_reset'", 367 user.id 368 ) 369 + .fetch_one(pool) 370 .await 371 .expect("Failed to count") 372 .unwrap_or(0); ··· 384 "SELECT COUNT(*) FROM comms_queue WHERE user_id = $1 AND comms_type = 'password_reset'", 385 user.id 386 ) 387 + .fetch_one(pool) 388 .await 389 .expect("Failed to count") 390 .unwrap_or(0);
+6 -16
tests/signing_key.rs
··· 3 use helpers::verify_new_account; 4 use reqwest::StatusCode; 5 use serde_json::{Value, json}; 6 - use sqlx::PgPool; 7 - 8 - async fn get_pool() -> PgPool { 9 - let conn_str = common::get_db_connection_string().await; 10 - sqlx::postgres::PgPoolOptions::new() 11 - .max_connections(5) 12 - .connect(&conn_str) 13 - .await 14 - .expect("Failed to connect to test database") 15 - } 16 17 #[tokio::test] 18 async fn test_reserve_signing_key_without_did() { ··· 41 async fn test_reserve_signing_key_with_did() { 42 let client = common::client(); 43 let base_url = common::base_url().await; 44 - let pool = get_pool().await; 45 let target_did = "did:plc:test123456"; 46 let res = client 47 .post(format!( ··· 60 "SELECT did, public_key_did_key FROM reserved_signing_keys WHERE public_key_did_key = $1", 61 signing_key 62 ) 63 - .fetch_one(&pool) 64 .await 65 .expect("Reserved key not found in database"); 66 assert_eq!(row.did.as_deref(), Some(target_did)); ··· 71 async fn test_reserve_signing_key_stores_private_key() { 72 let client = common::client(); 73 let base_url = common::base_url().await; 74 - let pool = get_pool().await; 75 let res = client 76 .post(format!( 77 "{}/xrpc/com.atproto.server.reserveSigningKey", ··· 88 "SELECT private_key_bytes, expires_at, used_at FROM reserved_signing_keys WHERE public_key_did_key = $1", 89 signing_key 90 ) 91 - .fetch_one(&pool) 92 .await 93 .expect("Reserved key not found in database"); 94 assert_eq!( ··· 161 async fn test_create_account_with_reserved_signing_key() { 162 let client = common::client(); 163 let base_url = common::base_url().await; 164 - let pool = get_pool().await; 165 let res = client 166 .post(format!( 167 "{}/xrpc/com.atproto.server.reserveSigningKey", ··· 199 "SELECT used_at FROM reserved_signing_keys WHERE public_key_did_key = $1", 200 signing_key 201 ) 202 - .fetch_one(&pool) 203 .await 204 .expect("Reserved key not found"); 205 assert!(
··· 3 use helpers::verify_new_account; 4 use reqwest::StatusCode; 5 use serde_json::{Value, json}; 6 7 #[tokio::test] 8 async fn test_reserve_signing_key_without_did() { ··· 31 async fn test_reserve_signing_key_with_did() { 32 let client = common::client(); 33 let base_url = common::base_url().await; 34 + let pool = common::get_test_db_pool().await; 35 let target_did = "did:plc:test123456"; 36 let res = client 37 .post(format!( ··· 50 "SELECT did, public_key_did_key FROM reserved_signing_keys WHERE public_key_did_key = $1", 51 signing_key 52 ) 53 + .fetch_one(pool) 54 .await 55 .expect("Reserved key not found in database"); 56 assert_eq!(row.did.as_deref(), Some(target_did)); ··· 61 async fn test_reserve_signing_key_stores_private_key() { 62 let client = common::client(); 63 let base_url = common::base_url().await; 64 + let pool = common::get_test_db_pool().await; 65 let res = client 66 .post(format!( 67 "{}/xrpc/com.atproto.server.reserveSigningKey", ··· 78 "SELECT private_key_bytes, expires_at, used_at FROM reserved_signing_keys WHERE public_key_did_key = $1", 79 signing_key 80 ) 81 + .fetch_one(pool) 82 .await 83 .expect("Reserved key not found in database"); 84 assert_eq!( ··· 151 async fn test_create_account_with_reserved_signing_key() { 152 let client = common::client(); 153 let base_url = common::base_url().await; 154 + let pool = common::get_test_db_pool().await; 155 let res = client 156 .post(format!( 157 "{}/xrpc/com.atproto.server.reserveSigningKey", ··· 189 "SELECT used_at FROM reserved_signing_keys WHERE public_key_did_key = $1", 190 signing_key 191 ) 192 + .fetch_one(pool) 193 .await 194 .expect("Reserved key not found"); 195 assert!(