• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polyphony-chat / sonata / 16352596362

17 Jul 2025 06:07PM UTC coverage: 81.241% (-6.4%) from 87.627%
16352596362

push

github

bitfl0wer
fix: fix failing tests

17 of 54 branches covered (31.48%)

Branch coverage included in aggregate %.

18 of 18 new or added lines in 2 files covered. (100.0%)

19 existing lines in 3 files now uncovered.

520 of 607 relevant lines covered (85.67%)

307.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.16
/src/database/models/mod.rs
1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4

5
use chrono::NaiveDateTime;
6
use sqlx::{query, query_as, types::Uuid};
7

8
use crate::{
9
        database::Database,
10
        errors::{Context, Errcode, Error},
11
};
12

13
#[derive(sqlx::FromRow, sqlx::Type)]
14
pub struct PemEncoded(String);
15

16
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17
pub enum ActorType {
18
        Local,
19
        Foreign,
20
}
21

22
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23
pub struct Actor {
24
        pub unique_actor_identifier: Uuid,
25
        r#type: ActorType,
26
}
27

28
impl From<LocalActor> for Actor {
29
        #[cfg_attr(coverage_nightly, coverage(off))]
30
        fn from(value: LocalActor) -> Self {
31
                Self { unique_actor_identifier: value.unique_actor_identifier, r#type: ActorType::Local }
32
        }
33
}
34

35
#[derive(Debug, sqlx::Decode, sqlx::Encode, sqlx::FromRow)]
36
/// Actors from this home server. Does not include the `password_hash` column.
37
pub struct LocalActor {
38
        /// The unique actor identifer. Does not change, even if the `local_name`
39
        /// changes.
40
        pub unique_actor_identifier: Uuid,
41
        /// The "local name" part of the actor. See the polyproto specification for
42
        /// more information.
43
        pub local_name: String,
44
        /// Whether this actors' account is currently deactivated.
45
        pub is_deactivated: bool,
46
        /// Timestamp from when the actor has first registered on the server, or
47
        /// when this account has been created.
48
        pub joined_at_timestamp: chrono::NaiveDateTime,
49
}
50

51
impl LocalActor {
52
        /// Tries to find an actor from the [Database] where `local_name` is equal
53
        /// to `name`, returning `None`, if such an actor does not exist.
54
        ///
55
        /// ## Errors
56
        ///
57
        /// Will error on Database connection issues and on other errors with the
58
        /// database, all of which are not in scope for this function to handle.
59
        pub async fn by_local_name(db: &Database, name: &str) -> Result<Option<LocalActor>, Error> {
20✔
60
                Ok(query!(
20✔
61
                        "
62
            SELECT uaid, local_name, deactivated, joined
63
            FROM local_actors
64
            WHERE local_name = $1
65
            LIMIT 1",
66
                        name
20!
67
                )
68
                .fetch_optional(&db.pool)
20✔
69
                .await?
20✔
70
                .map(|record| LocalActor {
20✔
71
                        unique_actor_identifier: record.uaid,
9✔
72
                        local_name: record.local_name,
9✔
73
                        is_deactivated: record.deactivated,
9✔
74
                        joined_at_timestamp: record.joined,
9✔
75
                }))
9✔
76
        }
20✔
77

78
        /// Returns the `password_hash` of an actor from the [Database] where
79
        /// `local_name` is equal to `name`, returning `None`, if such an actor
80
        /// does not exist.
81
        ///
82
        /// ## Errors
83
        ///
84
        /// Will error on Database connection issues and on other errors with the
85
        /// database, all of which are not in scope for this function to handle.
UNCOV
86
        pub async fn get_password_hash(db: &Database, name: &str) -> Result<Option<String>, Error> {
×
UNCOV
87
                Ok(query!(
×
88
                        "
89
            SELECT password_hash
90
            FROM local_actors
91
            WHERE local_name = $1
92
            LIMIT 1",
93
                        name
×
94
                )
UNCOV
95
                .fetch_optional(&db.pool)
×
UNCOV
96
                .await?
×
UNCOV
97
                .map(|record| record.password_hash))
×
UNCOV
98
        }
×
99

100
        /// Create a new [LocalActor] in the `local_actors` table of the [Database].
101
        /// Before creating, checks, if a user specified by `local_name` already
102
        /// exists in the table, returning an [Errcode::Duplicate]-type error, if
103
        /// this is the case.
104
        ///
105
        /// ## Errors
106
        ///
107
        /// Other than the above, this method will error, if something is wrong with
108
        /// the Database or Database connection.
109
        pub async fn create(
9✔
110
                db: &Database,
9✔
111
                local_name: &str,
9✔
112
                password_hash: &str,
9✔
113
        ) -> Result<LocalActor, Error> {
9✔
114
                if LocalActor::by_local_name(db, local_name).await?.is_some() {
9!
115
                        Err(Error::new(
2✔
116
                                Errcode::Duplicate,
2✔
117
                                Some(Context::new(Some("local_name"), Some(local_name), None, None)),
2✔
118
                        ))
2✔
119
                } else {
120
                        let uaid = query!("INSERT INTO actors (type) VALUES ('local') RETURNING uaid")
7✔
121
                                .fetch_one(&db.pool)
7✔
122
                                .await?;
7✔
123
                        Ok(query_as!(
7✔
124
                        LocalActor,
125
                        "INSERT INTO local_actors (uaid, local_name, password_hash) VALUES ($1, $2, $3) RETURNING uaid AS unique_actor_identifier, local_name, deactivated AS is_deactivated, joined AS joined_at_timestamp",
126
                        uaid.uaid,
7!
127
                        local_name,
7!
128
                        password_hash
7!
129
                ).fetch_one(&db.pool).await?)
7✔
130
                }
131
        }
9✔
132
}
133

134
#[derive(sqlx::Decode, sqlx::Encode, sqlx::FromRow)]
135
pub struct AlgorithmIdentifier {
136
        pub id: i32,
137
        pub algorithm_identifier_oid: String,
138
        pub common_name: Option<String>,
139
        pub parameters: Option<String>,
140
}
141

142
#[derive(sqlx::Decode, sqlx::Encode, sqlx::FromRow)]
143
pub struct PublicKey {
144
        pub id: i64,
145
        pub belongs_to_actor_identifier: Uuid,
146
        pub public_key: String,
147
        pub algorithm_identifier_id: i32,
148
}
149

150
#[derive(sqlx::Decode, sqlx::Encode, sqlx::FromRow)]
151
pub struct Subjects {
152
        pub actor_unique_identifier: Uuid,
153
        pub domain_components: Vec<String>,
154
        pub subject_x509_pem: PemEncoded,
155
}
156

157
#[derive(sqlx::Decode, sqlx::Encode, sqlx::FromRow)]
158
pub struct Issuers {
159
        pub id: i64,
160
        pub domain_components: Vec<String>,
161
        pub issuer_x509_pem: PemEncoded,
162
}
163

164
#[derive(sqlx::Decode, sqlx::Encode, sqlx::FromRow)]
165
pub struct IdCsr {
166
        pub internal_serial_number: Uuid,
167
        pub for_actor_uaid: Uuid,
168
        pub actor_public_key_id: i64,
169
        pub actor_signature: String,
170
        pub session_id: String, // TODO make this serialnumba
171
        pub valid_not_before: NaiveDateTime,
172
        pub valid_not_after: NaiveDateTime,
173
        pub extensions: String,
174
        pub pem_encoded: String,
175
}
176

177
#[derive(sqlx::Decode, sqlx::Encode, sqlx::FromRow)]
178
pub struct Invite {
179
        pub invite_link_owner: Option<Uuid>,
180
        pub usages_current: i32,
181
        pub usages_maximum: i32,
182
        pub invite_code: String,
183
        pub invalid: bool,
184
}
185

186
#[cfg(test)]
187
mod tests {
188
        use sqlx::{Pool, Postgres};
189

190
        use super::*;
191

192
        #[test]
193
        fn test_algorithm_identifier_creation() {
1✔
194
                let algo_id = AlgorithmIdentifier {
1✔
195
                        id: 1,
1✔
196
                        algorithm_identifier_oid: "1.2.840.113549.1.1.11".to_string(),
1✔
197
                        common_name: Some("SHA256withRSA".to_string()),
1✔
198
                        parameters: Some("null".to_string()),
1✔
199
                };
1✔
200

201
                assert_eq!(algo_id.id, 1);
1✔
202
                assert_eq!(algo_id.algorithm_identifier_oid, "1.2.840.113549.1.1.11");
1✔
203
                assert_eq!(algo_id.common_name, Some("SHA256withRSA".to_string()));
1✔
204
                assert_eq!(algo_id.parameters, Some("null".to_string()));
1✔
205
        }
1✔
206

207
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
208
        async fn test_by_local_name_finds_existing_user(pool: Pool<Postgres>) {
209
                let db = Database { pool };
210

211
                let result = LocalActor::by_local_name(&db, "alice").await.unwrap();
212
                assert!(result.is_some());
213

214
                let actor = result.unwrap();
215
                assert_eq!(actor.local_name, "alice");
216
                assert_eq!(
217
                        actor.unique_actor_identifier.to_string(),
218
                        "00000000-0000-0000-0000-000000000001"
219
                );
220
                assert!(!actor.is_deactivated);
221
        }
222

223
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
224
        async fn test_by_local_name_finds_deactivated_user(pool: Pool<Postgres>) {
225
                let db = Database { pool };
226

227
                let result = LocalActor::by_local_name(&db, "deactivated_user").await.unwrap();
228
                assert!(result.is_some());
229

230
                let actor = result.unwrap();
231
                assert_eq!(actor.local_name, "deactivated_user");
232
                assert_eq!(
233
                        actor.unique_actor_identifier.to_string(),
234
                        "00000000-0000-0000-0000-000000000004"
235
                );
236
                assert!(actor.is_deactivated);
237
        }
238

239
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
240
        async fn test_by_local_name_finds_user_with_special_characters(pool: Pool<Postgres>) {
241
                let db = Database { pool };
242

243
                let result = LocalActor::by_local_name(&db, "user_with_underscores").await.unwrap();
244
                assert!(result.is_some());
245

246
                let actor = result.unwrap();
247
                assert_eq!(actor.local_name, "user_with_underscores");
248
                assert_eq!(
249
                        actor.unique_actor_identifier.to_string(),
250
                        "00000000-0000-0000-0000-000000000005"
251
                );
252
                assert!(!actor.is_deactivated);
253
        }
254

255
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
256
        async fn test_by_local_name_returns_none_for_nonexistent_user(pool: Pool<Postgres>) {
257
                let db = Database { pool };
258

259
                let result = LocalActor::by_local_name(&db, "nonexistent_user").await.unwrap();
260
                assert!(result.is_none());
261
        }
262

263
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
264
        async fn test_by_local_name_returns_none_for_empty_string(pool: Pool<Postgres>) {
265
                let db = Database { pool };
266

267
                let result = LocalActor::by_local_name(&db, "").await.unwrap();
268
                assert!(result.is_none());
269
        }
270

271
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
272
        async fn test_by_local_name_is_case_sensitive(pool: Pool<Postgres>) {
273
                let db = Database { pool };
274

275
                // Should find exact match
276
                let result_exact = LocalActor::by_local_name(&db, "alice").await.unwrap();
277
                assert!(result_exact.is_some());
278

279
                // Should not find case-different match
280
                let result_upper = LocalActor::by_local_name(&db, "ALICE").await.unwrap();
281
                assert!(result_upper.is_none());
282

283
                let result_mixed = LocalActor::by_local_name(&db, "Alice").await.unwrap();
284
                assert!(result_mixed.is_none());
285
        }
286

287
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
288
        async fn test_create_new_user_success(pool: Pool<Postgres>) {
289
                let db = Database { pool };
290

291
                let result = LocalActor::create(&db, "new_user", "hash").await;
292
                assert!(result.is_ok());
293

294
                let actor = result.unwrap();
295
                assert_eq!(actor.local_name, "new_user");
296
                assert!(!actor.is_deactivated);
297
                assert!(actor.unique_actor_identifier != sqlx::types::Uuid::nil());
298

299
                // Verify the user was actually created in the database
300
                let found = LocalActor::by_local_name(&db, "new_user").await.unwrap();
301
                assert!(found.is_some());
302
                let found_actor = found.unwrap();
303
                assert_eq!(found_actor.unique_actor_identifier, actor.unique_actor_identifier);
304
        }
305

306
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
307
        async fn test_create_duplicate_user_returns_error(pool: Pool<Postgres>) {
308
                let db = Database { pool };
309

310
                let result = LocalActor::create(&db, "alice", "hash").await;
311
                assert!(result.is_err());
312

313
                match result.unwrap_err() {
314
                        error => {
315
                                assert_eq!(error.code, Errcode::Duplicate);
316
                                assert!(error.context.is_some());
317
                                let context = error.context.unwrap();
318
                                assert_eq!(context.field_name, "local_name");
319
                                assert_eq!(context.found, "alice");
320
                        }
321
                }
322
        }
323

324
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
325
        async fn test_create_duplicate_deactivated_user_returns_error(pool: Pool<Postgres>) {
326
                let db = Database { pool };
327

328
                let result = LocalActor::create(&db, "deactivated_user", "hash").await;
329
                assert!(result.is_err());
330

331
                match result.unwrap_err() {
332
                        error => {
333
                                assert_eq!(error.code, Errcode::Duplicate);
334
                                assert!(error.context.is_some());
335
                                let context = error.context.unwrap();
336
                                assert_eq!(context.field_name, "local_name");
337
                                assert_eq!(context.found, "deactivated_user");
338
                        }
339
                }
340
        }
341

342
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
343
        async fn test_create_user_with_special_characters(pool: Pool<Postgres>) {
344
                let db = Database { pool };
345

346
                let result = LocalActor::create(&db, "user.with-special_chars", "hash").await;
347
                assert!(result.is_ok());
348

349
                let actor = result.unwrap();
350
                assert_eq!(actor.local_name, "user.with-special_chars");
351
                assert!(!actor.is_deactivated);
352

353
                let found = LocalActor::by_local_name(&db, "user.with-special_chars").await.unwrap();
354
                assert!(found.is_some());
355
        }
356

357
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
358
        async fn test_create_user_with_empty_name(pool: Pool<Postgres>) {
359
                let db = Database { pool };
360

361
                let result = LocalActor::create(&db, "", "hash").await;
362
                assert!(result.is_ok());
363

364
                let actor = result.unwrap();
365
                assert_eq!(actor.local_name, "");
366
                assert!(!actor.is_deactivated);
367

368
                let found = LocalActor::by_local_name(&db, "").await.unwrap();
369
                assert!(found.is_some());
370
        }
371

372
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
373
        async fn test_create_multiple_users_have_different_uuids(pool: Pool<Postgres>) {
374
                let db = Database { pool };
375

376
                let user1 = LocalActor::create(&db, "user1", "hash").await.unwrap();
377
                let user2 = LocalActor::create(&db, "user2", "hash").await.unwrap();
378
                let user3 = LocalActor::create(&db, "user3", "hash").await.unwrap();
379

380
                assert_ne!(user1.unique_actor_identifier, user2.unique_actor_identifier);
381
                assert_ne!(user1.unique_actor_identifier, user3.unique_actor_identifier);
382
                assert_ne!(user2.unique_actor_identifier, user3.unique_actor_identifier);
383

384
                assert_ne!(user1.local_name, user2.local_name);
385
                assert_ne!(user1.local_name, user3.local_name);
386
                assert_ne!(user2.local_name, user3.local_name);
387
        }
388

389
        #[sqlx::test(fixtures("../../../fixtures/local_actor_tests.sql"))]
390
        async fn test_create_user_sets_joined_timestamp(pool: Pool<Postgres>) {
391
                let db = Database { pool };
392

393
                let before_create = chrono::Utc::now().naive_utc();
394
                let actor = LocalActor::create(&db, "timestamped_user", "hash").await.unwrap();
395
                let after_create = chrono::Utc::now().naive_utc();
396

397
                assert!(actor.joined_at_timestamp >= before_create);
398
                assert!(actor.joined_at_timestamp <= after_create);
399
        }
400
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc