Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit 606aa9c

Browse files
author
bitfl0wer
committed
feat: late-night brainless tests and coverage
1 parent fc91285 commit 606aa9c

File tree

9 files changed

+241
-8
lines changed

9 files changed

+241
-8
lines changed

src/api/auth/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use poem::Route;
33
mod login;
44
mod register;
55

6+
#[cfg_attr(coverage_nightly, coverage(off))]
67
pub fn setup_routes() -> Route {
78
Route::new()
89
}

src/api/auth/register.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use argon2::{
22
Argon2,
3-
password_hash::{PasswordHash, PasswordHasher, SaltString, rand_core::OsRng},
3+
password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
44
};
55
use poem::{
66
IntoResponse, handler,
@@ -14,6 +14,7 @@ use crate::{
1414
errors::{Context, Errcode, Error, SonataApiError},
1515
};
1616

17+
#[cfg_attr(coverage_nightly, coverage(off))]
1718
#[handler]
1819
pub async fn register(
1920
Json(payload): Json<RegisterSchema>,

src/api/middlewares/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::database::tokens::{TokenStore, hash_auth_token};
1010
/// [AuthenticationMiddlewareImpl]
1111
pub struct AuthenticationMiddleware;
1212

13+
#[cfg_attr(coverage_nightly, coverage(off))]
1314
impl<E: Endpoint> Middleware<E> for AuthenticationMiddleware {
1415
type Output = AuthenticationMiddlewareImpl<E>;
1516

@@ -25,6 +26,7 @@ pub struct AuthenticationMiddlewareImpl<E> {
2526
ep: E,
2627
}
2728

29+
#[cfg_attr(coverage_nightly, coverage(off))]
2830
impl<E: Endpoint> Endpoint for AuthenticationMiddlewareImpl<E> {
2931
type Output = E::Output;
3032

src/api/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub(super) fn start_api(
6262
handle
6363
}
6464

65+
#[cfg_attr(coverage_nightly, coverage(off))]
6566
/// Catch-all fallback error.
6667
async fn custom_error(err: poem::Error) -> impl IntoResponse {
6768
Json(json! ({
@@ -71,11 +72,13 @@ async fn custom_error(err: poem::Error) -> impl IntoResponse {
7172
.with_status(err.status())
7273
}
7374

75+
#[cfg_attr(coverage_nightly, coverage(off))]
7476
#[handler]
7577
fn healthz() -> impl IntoResponse {
7678
Response::builder().status(StatusCode::OK).finish()
7779
}
7880

81+
#[cfg_attr(coverage_nightly, coverage(off))]
7982
/// All routes under `/.p2/core/`.
8083
fn setup_p2_core_routes() -> Route {
8184
Route::new()

src/api/models/mod.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,168 @@ impl PasswordRequirements for NISTPasswordRequirements {
6868
Ok(password.to_owned())
6969
}
7070
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use serde_json::json;
75+
76+
use super::*;
77+
78+
#[test]
79+
fn test_register_schema_serialization() {
80+
let schema = RegisterSchema {
81+
tos_consent: true,
82+
local_name: "testuser".to_string(),
83+
password: "testpassword123".to_string(),
84+
invite: Some("invite123".to_string()),
85+
};
86+
87+
let serialized = serde_json::to_string(&schema).unwrap();
88+
let parsed: serde_json::Value = serde_json::from_str(&serialized).unwrap();
89+
90+
assert_eq!(parsed["tosConsent"], true);
91+
assert_eq!(parsed["localName"], "testuser");
92+
assert_eq!(parsed["password"], "testpassword123");
93+
assert_eq!(parsed["invite"], "invite123");
94+
}
95+
96+
#[test]
97+
fn test_register_schema_deserialization() {
98+
let json_str = r#"{"tosConsent":true,"localName":"testuser","password":"testpassword123","invite":"invite123"}"#;
99+
let schema: RegisterSchema = serde_json::from_str(json_str).unwrap();
100+
101+
assert_eq!(schema.tos_consent, true);
102+
assert_eq!(schema.local_name, "testuser");
103+
assert_eq!(schema.password, "testpassword123");
104+
assert_eq!(schema.invite, Some("invite123".to_string()));
105+
}
106+
107+
#[test]
108+
fn test_nist_password_requirements_valid_password() {
109+
let result = NISTPasswordRequirements::verify_requirements("password123");
110+
assert!(result.is_ok());
111+
assert_eq!(result.unwrap(), "password123");
112+
}
113+
114+
#[test]
115+
fn test_nist_password_requirements_minimum_length() {
116+
let result = NISTPasswordRequirements::verify_requirements("12345678");
117+
assert!(result.is_ok());
118+
assert_eq!(result.unwrap(), "12345678");
119+
}
120+
121+
#[test]
122+
fn test_nist_password_requirements_maximum_length() {
123+
let long_password = "a".repeat(64);
124+
let result = NISTPasswordRequirements::verify_requirements(&long_password);
125+
assert!(result.is_ok());
126+
assert_eq!(result.unwrap(), long_password);
127+
}
128+
129+
#[test]
130+
fn test_nist_password_requirements_too_short() {
131+
let result = NISTPasswordRequirements::verify_requirements("1234567");
132+
assert!(result.is_err());
133+
let error = result.unwrap_err();
134+
assert_eq!(error.code, crate::errors::Errcode::IllegalInput);
135+
assert!(error.context.is_some());
136+
let context = error.context.unwrap();
137+
assert_eq!(context.field_name, "password");
138+
assert_eq!(context.found, "7 characters");
139+
assert_eq!(context.expected, "More than 7 and less than 65 characters");
140+
}
141+
142+
#[test]
143+
fn test_nist_password_requirements_too_long() {
144+
let long_password = "a".repeat(65);
145+
let result = NISTPasswordRequirements::verify_requirements(&long_password);
146+
assert!(result.is_err());
147+
let error = result.unwrap_err();
148+
assert_eq!(error.code, crate::errors::Errcode::IllegalInput);
149+
assert!(error.context.is_some());
150+
let context = error.context.unwrap();
151+
assert_eq!(context.field_name, "password");
152+
assert_eq!(context.found, "65 characters");
153+
assert_eq!(context.expected, "More than 7 and less than 65 characters");
154+
}
155+
156+
#[test]
157+
fn test_nist_password_requirements_empty_password() {
158+
let result = NISTPasswordRequirements::verify_requirements("");
159+
assert!(result.is_err());
160+
let error = result.unwrap_err();
161+
assert_eq!(error.code, crate::errors::Errcode::IllegalInput);
162+
assert!(error.context.is_some());
163+
let context = error.context.unwrap();
164+
assert_eq!(context.field_name, "password");
165+
assert_eq!(context.found, "0 characters");
166+
assert_eq!(context.expected, "More than 7 and less than 65 characters");
167+
}
168+
169+
#[test]
170+
fn test_nist_password_requirements_unicode_characters() {
171+
let unicode_password = "пароль123🔐";
172+
let result = NISTPasswordRequirements::verify_requirements(unicode_password);
173+
assert!(result.is_ok());
174+
assert_eq!(result.unwrap(), unicode_password);
175+
}
176+
177+
#[test]
178+
fn test_nist_password_requirements_spaces_allowed() {
179+
let password_with_spaces = "password with spaces";
180+
let result = NISTPasswordRequirements::verify_requirements(password_with_spaces);
181+
assert!(result.is_ok());
182+
assert_eq!(result.unwrap(), password_with_spaces);
183+
}
184+
185+
#[test]
186+
fn test_nist_password_requirements_special_characters() {
187+
let special_password = "!@#$%^&*()_+-=[]{}|;':\",./<>?";
188+
let result = NISTPasswordRequirements::verify_requirements(special_password);
189+
assert!(result.is_ok());
190+
assert_eq!(result.unwrap(), special_password);
191+
}
192+
193+
#[test]
194+
fn test_nist_password_requirements_mixed_case() {
195+
let mixed_case_password = "AbCdEfGhIjKl";
196+
let result = NISTPasswordRequirements::verify_requirements(mixed_case_password);
197+
assert!(result.is_ok());
198+
assert_eq!(result.unwrap(), mixed_case_password);
199+
}
200+
201+
#[test]
202+
fn test_nist_password_requirements_numbers_only() {
203+
let numbers_only = "12345678";
204+
let result = NISTPasswordRequirements::verify_requirements(numbers_only);
205+
assert!(result.is_ok());
206+
assert_eq!(result.unwrap(), numbers_only);
207+
}
208+
209+
#[test]
210+
fn test_nist_password_requirements_letters_only() {
211+
let letters_only = "abcdefgh";
212+
let result = NISTPasswordRequirements::verify_requirements(letters_only);
213+
assert!(result.is_ok());
214+
assert_eq!(result.unwrap(), letters_only);
215+
}
216+
217+
#[test]
218+
fn test_nist_password_requirements_boundary_values() {
219+
// Test exactly 8 characters
220+
let min_valid = "12345678";
221+
assert!(NISTPasswordRequirements::verify_requirements(min_valid).is_ok());
222+
223+
// Test exactly 64 characters
224+
let max_valid = "a".repeat(64);
225+
assert!(NISTPasswordRequirements::verify_requirements(&max_valid).is_ok());
226+
227+
// Test exactly 7 characters (should fail)
228+
let too_short = "1234567";
229+
assert!(NISTPasswordRequirements::verify_requirements(too_short).is_err());
230+
231+
// Test exactly 65 characters (should fail)
232+
let too_long = "a".repeat(65);
233+
assert!(NISTPasswordRequirements::verify_requirements(&too_long).is_err());
234+
}
235+
}

src/config/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ impl SonataConfig {
135135
}
136136
}
137137

138-
#[derive(Debug, Deserialize, Default, Clone)]
138+
#[derive(Debug, Deserialize, Default, Clone, PartialEq)]
139139
/// TLS configuration modes. Also called `sslconfig` by PostgreSQL. See <https://www.postgresql.org/docs/current/libpq-ssl.html#:~:text=32.1.%C2%A0SSL%20Mode-,descriptions,-sslmode>
140140
/// for the security implications of this choice.
141141
pub enum TlsConfig {

src/database/mod.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,24 @@ mod tests {
9696
assert!(result.is_err());
9797
}
9898

99-
// Note: Testing actual database connections and migrations would require
100-
// either a test database or mocking, which is typically done in integration
101-
// tests
99+
#[tokio::test]
100+
async fn test_connect_with_config_zero_max_connections() {
101+
let config = DatabaseConfig {
102+
max_connections: 0, // Zero connections should cause a panic during pool creation
103+
database: "test".to_owned(),
104+
username: "test".to_owned(),
105+
password: "test".to_owned(),
106+
port: 5432,
107+
host: "localhost".to_owned(),
108+
tls: TlsConfig::Disable,
109+
};
110+
111+
// This should panic or error due to zero max_connections
112+
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
113+
tokio::runtime::Runtime::new()
114+
.unwrap()
115+
.block_on(async { Database::connect_with_config(&config).await })
116+
}));
117+
assert!(result.is_err());
118+
}
102119
}

src/database/models/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,23 @@ pub struct Invite {
158158
pub invite_code: String,
159159
pub invalid: bool,
160160
}
161+
162+
#[cfg(test)]
163+
mod tests {
164+
use super::*;
165+
166+
#[test]
167+
fn test_algorithm_identifier_creation() {
168+
let algo_id = AlgorithmIdentifier {
169+
id: 1,
170+
algorithm_identifier_oid: "1.2.840.113549.1.1.11".to_string(),
171+
common_name: Some("SHA256withRSA".to_string()),
172+
parameters: Some("null".to_string()),
173+
};
174+
175+
assert_eq!(algo_id.id, 1);
176+
assert_eq!(algo_id.algorithm_identifier_oid, "1.2.840.113549.1.1.11");
177+
assert_eq!(algo_id.common_name, Some("SHA256withRSA".to_string()));
178+
assert_eq!(algo_id.parameters, Some("null".to_string()));
179+
}
180+
}

src/errors.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl Error {
6464
}
6565
}
6666

67-
#[derive(Debug, Clone, Copy, Display, Serialize, Deserialize)]
67+
#[derive(Debug, Clone, Copy, Display, Serialize, Deserialize, PartialEq)]
6868
/// Standardized polyproto core error codes, giving a rough idea of what went
6969
/// wrong.
7070
pub enum Errcode {
@@ -191,7 +191,6 @@ pub(crate) enum SonataDbError {
191191
Sqlx(#[from] sqlx::Error),
192192
}
193193

194-
#[cfg_attr(coverage_nightly, coverage(off))]
195194
impl ResponseError for SonataApiError {
196195
fn status(&self) -> poem::http::StatusCode {
197196
match self {
@@ -212,7 +211,6 @@ impl IntoResponse for SonataApiError {
212211
}
213212
}
214213

215-
#[cfg_attr(coverage_nightly, coverage(off))]
216214
impl ResponseError for SonataDbError {
217215
fn status(&self) -> poem::http::StatusCode {
218216
match self {
@@ -221,3 +219,29 @@ impl ResponseError for SonataDbError {
221219
}
222220
}
223221
}
222+
223+
#[cfg(test)]
224+
mod tests {
225+
use super::*;
226+
227+
#[test]
228+
fn test_error_serialization() {
229+
let context = Context::new(Some("field"), Some("value"), Some("expected"));
230+
let error = Error {
231+
code: Errcode::IllegalInput,
232+
message: "Test message".to_string(),
233+
context: Some(context),
234+
};
235+
236+
let serialized = serde_json::to_string(&error).unwrap();
237+
let deserialized: Error = serde_json::from_str(&serialized).unwrap();
238+
239+
assert_eq!(deserialized.code, error.code);
240+
assert_eq!(deserialized.message, error.message);
241+
assert!(deserialized.context.is_some());
242+
let ctx = deserialized.context.unwrap();
243+
assert_eq!(ctx.field_name, "field");
244+
assert_eq!(ctx.found, "value");
245+
assert_eq!(ctx.expected, "expected");
246+
}
247+
}

0 commit comments

Comments
 (0)