227 lines
5.9 KiB
Rust
227 lines
5.9 KiB
Rust
#![cfg(feature = "sqlite")]
|
|
|
|
use nx9_auth::db::{
|
|
self,
|
|
provider::{DatabaseProvider, SqliteProvider},
|
|
};
|
|
use std::sync::Arc;
|
|
|
|
async fn setup_test_provider() -> (Arc<SqliteProvider>, String) {
|
|
let db_id = uuid::Uuid::new_v4().to_string();
|
|
let db_path = format!("target/test_audit_export_{}.db", db_id);
|
|
let pool = db::create_pool(&db_path)
|
|
.await
|
|
.expect("Failed to create test pool");
|
|
db::run_migrations(&pool)
|
|
.await
|
|
.expect("Failed to run migrations");
|
|
(Arc::new(SqliteProvider::new(pool)), db_path)
|
|
}
|
|
|
|
async fn teardown_test_db(path: String) {
|
|
let _ = std::fs::remove_file(path);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_audit_list_remains_clamped_to_500() {
|
|
let (provider, db_path) = setup_test_provider().await;
|
|
|
|
// Insert 600 audit events
|
|
for i in 0..600 {
|
|
let audit_id = uuid::Uuid::new_v4().to_string();
|
|
provider
|
|
.audit()
|
|
.insert(
|
|
&audit_id,
|
|
None,
|
|
None,
|
|
"user.login",
|
|
"user",
|
|
None,
|
|
"info",
|
|
Some("127.0.0.1"),
|
|
Some("TestRunner"),
|
|
Some(&format!("{{\"index\": {i}}}")),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
// Normal list filter with limit=1000 MUST be clamped to 500 by backend API logic
|
|
let filter = nx9_auth::db::models::AuditFilter {
|
|
limit: 1000,
|
|
..Default::default()
|
|
};
|
|
let clamped_limit = filter.limit.clamp(1, 500);
|
|
assert_eq!(clamped_limit, 500);
|
|
|
|
let entries = provider
|
|
.audit()
|
|
.list_filtered(&nx9_auth::db::models::AuditFilter {
|
|
limit: clamped_limit,
|
|
..Default::default()
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
entries.len(),
|
|
500,
|
|
"Normal audit listing must be bounded to 500 records max"
|
|
);
|
|
|
|
teardown_test_db(db_path).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_audit_export_can_return_more_than_500_up_to_5000() {
|
|
let (provider, db_path) = setup_test_provider().await;
|
|
|
|
// Insert 600 audit events
|
|
for i in 0..600 {
|
|
let audit_id = uuid::Uuid::new_v4().to_string();
|
|
provider
|
|
.audit()
|
|
.insert(
|
|
&audit_id,
|
|
None,
|
|
None,
|
|
"user.login",
|
|
"user",
|
|
None,
|
|
"info",
|
|
Some("127.0.0.1"),
|
|
Some("TestRunner"),
|
|
Some(&format!("{{\"index\": {i}}}")),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
// Export query path with limit=5000 returns all 600 records (>500)
|
|
let export_filter = nx9_auth::db::models::AuditFilter {
|
|
limit: 5000,
|
|
..Default::default()
|
|
};
|
|
|
|
let entries = provider
|
|
.audit()
|
|
.list_filtered(&export_filter)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
entries.len(),
|
|
600,
|
|
"Export query path must return >500 records when available"
|
|
);
|
|
|
|
teardown_test_db(db_path).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_audit_export_hard_bounded_at_5000() {
|
|
let (_provider, db_path) = setup_test_provider().await;
|
|
|
|
// Request limit 999999 must be clamped to hard maximum 5000
|
|
let requested_limit = 999999i64;
|
|
let export_limit = requested_limit.clamp(1, 5000);
|
|
assert_eq!(export_limit, 5000);
|
|
|
|
teardown_test_db(db_path).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_server_side_success_filtering_before_limit_and_count() {
|
|
let (provider, db_path) = setup_test_provider().await;
|
|
|
|
// Insert 10 success events and 10 failure events
|
|
for _ in 0..10 {
|
|
let audit_id = uuid::Uuid::new_v4().to_string();
|
|
provider
|
|
.audit()
|
|
.insert(
|
|
&audit_id,
|
|
None,
|
|
None,
|
|
"user.login",
|
|
"user",
|
|
None,
|
|
"info",
|
|
Some("127.0.0.1"),
|
|
Some("TestRunner"),
|
|
None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
for _ in 0..10 {
|
|
let audit_id = uuid::Uuid::new_v4().to_string();
|
|
provider
|
|
.audit()
|
|
.insert(
|
|
&audit_id,
|
|
None,
|
|
None,
|
|
"auth.failed",
|
|
"user",
|
|
None,
|
|
"warning",
|
|
Some("127.0.0.1"),
|
|
Some("TestRunner"),
|
|
None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
// Server-side filter success = true
|
|
let success_filter = nx9_auth::db::models::AuditFilter {
|
|
success: Some(true),
|
|
limit: 50,
|
|
..Default::default()
|
|
};
|
|
|
|
let count = provider
|
|
.audit()
|
|
.count_filtered(&success_filter)
|
|
.await
|
|
.unwrap();
|
|
let entries = provider
|
|
.audit()
|
|
.list_filtered(&success_filter)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(count, 10);
|
|
assert_eq!(entries.len(), 10);
|
|
assert!(entries.iter().all(|e| !e.action.contains("fail")));
|
|
|
|
// Server-side filter success = false
|
|
let fail_filter = nx9_auth::db::models::AuditFilter {
|
|
success: Some(false),
|
|
limit: 50,
|
|
..Default::default()
|
|
};
|
|
|
|
let fail_count = provider.audit().count_filtered(&fail_filter).await.unwrap();
|
|
let fail_entries = provider.audit().list_filtered(&fail_filter).await.unwrap();
|
|
|
|
assert_eq!(fail_count, 10);
|
|
assert_eq!(fail_entries.len(), 10);
|
|
assert!(fail_entries.iter().all(|e| e.action.contains("fail")));
|
|
|
|
teardown_test_db(db_path).await;
|
|
}
|
|
|
|
#[test]
|
|
fn test_rfc4180_csv_escaping_rules() {
|
|
let esc = |s: &str| format!("\"{}\"", s.replace('"', "\"\""));
|
|
|
|
assert_eq!(esc("simple"), "\"simple\"");
|
|
assert_eq!(esc("with,comma"), "\"with,comma\"");
|
|
assert_eq!(esc("with \"quotes\""), "\"with \"\"quotes\"\"\"");
|
|
assert_eq!(esc("multi\nline"), "\"multi\nline\"");
|
|
}
|