diff --git a/Cargo.lock b/Cargo.lock index e9452dacec..e0a9366b1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1342,6 +1342,7 @@ dependencies = [ "hyper-util", "ipnetwork", "itertools 0.14.0", + "jsonwebtoken", "kube", "lazy_static", "libnmxm", diff --git a/crates/admin-cli/src/rpc.rs b/crates/admin-cli/src/rpc.rs index 74b9cde539..a445820a5a 100644 --- a/crates/admin-cli/src/rpc.rs +++ b/crates/admin-cli/src/rpc.rs @@ -1422,10 +1422,15 @@ impl ApiClient { run_unverfied_tests: bool, contexts: Option>, ) -> CarbideCliResult { + let allowed_tests: Vec = allowed_tests + .unwrap_or_default() + .into_iter() + .map(|t| t.to_ascii_lowercase()) + .collect(); let request = rpc::MachineValidationOnDemandRequest { machine_id: Some(machine_id), tags: tags.unwrap_or_default(), - allowed_tests: allowed_tests.unwrap_or_default(), + allowed_tests, action: rpc::machine_validation_on_demand_request::Action::Start.into(), run_unverfied_tests, contexts: contexts.unwrap_or_default(), diff --git a/crates/api-db/src/machine_validation_suites.rs b/crates/api-db/src/machine_validation_suites.rs index 796edd9a74..a294aaade2 100644 --- a/crates/api-db/src/machine_validation_suites.rs +++ b/crates/api-db/src/machine_validation_suites.rs @@ -179,7 +179,7 @@ fn build_select_query( for (key, value) in json_object { if !value.is_null() { match value { - serde_json::Value::String(s) => wheres.push(format!("{key}='{s}'")), + serde_json::Value::String(s) => wheres.push(format!("LOWER({key})=LOWER('{s}')")), serde_json::Value::Number(n) => wheres.push(format!("{key}={n}")), serde_json::Value::Bool(b) => wheres.push(format!("{key}={b}")), serde_json::Value::Array(v) => { @@ -221,7 +221,7 @@ pub async fn find( } pub fn generate_test_id(name: &str) -> String { - format!("forge_{name}") + format!("forge_{}", name.to_ascii_lowercase()) } pub async fn save( @@ -349,3 +349,60 @@ pub async fn enable_disable( }; update(txn, req).await } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_test_id_lowercases_name() { + assert_eq!(generate_test_id("MyTest"), "forge_mytest"); + assert_eq!(generate_test_id("ALLCAPS"), "forge_allcaps"); + assert_eq!(generate_test_id("already_lower"), "forge_already_lower"); + assert_eq!(generate_test_id("MiXeD_CaSe_123"), "forge_mixed_case_123"); + } + + #[test] + fn test_build_select_query_uses_lower_for_strings() { + let req = rpc::forge::MachineValidationTestsGetRequest { + test_id: Some("Forge_MyTest".to_string()), + ..Default::default() + }; + let query = build_select_query(req, "machine_validation_tests").unwrap(); + assert!( + query.contains("LOWER("), + "Expected LOWER() in query, got: {query}" + ); + assert!( + query.contains("LOWER(test_id)=LOWER('Forge_MyTest')"), + "Expected case-insensitive test_id comparison, got: {query}" + ); + } + + #[test] + fn test_build_select_query_no_lower_for_non_strings() { + let req = rpc::forge::MachineValidationTestsGetRequest { + is_enabled: Some(true), + ..Default::default() + }; + let query = build_select_query(req, "machine_validation_tests").unwrap(); + assert!( + query.contains("is_enabled=true"), + "Boolean fields should not use LOWER(), got: {query}" + ); + } + + #[test] + fn test_build_select_query_empty_request_returns_all() { + let req = rpc::forge::MachineValidationTestsGetRequest::default(); + let query = build_select_query(req, "machine_validation_tests").unwrap(); + assert!( + query.contains("WHERE 1=1"), + "Empty request should have no extra filters, got: {query}" + ); + assert!( + !query.contains("LOWER("), + "Empty request should have no LOWER(), got: {query}" + ); + } +} diff --git a/crates/api/src/handlers/machine_validation.rs b/crates/api/src/handlers/machine_validation.rs index b4fcddd0a8..00bf3a8178 100644 --- a/crates/api/src/handlers/machine_validation.rs +++ b/crates/api/src/handlers/machine_validation.rs @@ -416,13 +416,18 @@ pub(crate) async fn on_demand_machine_validation( tracing::error!(msg); return Err(Status::invalid_argument(msg)); } + let allowed_tests: Vec = req + .allowed_tests + .into_iter() + .map(|t| t.to_ascii_lowercase()) + .collect(); let validation_id = db::machine_validation::create_new_run( &mut txn, &machine_id, "OnDemand".to_string(), MachineValidationFilter { tags: req.tags, - allowed_tests: req.allowed_tests, + allowed_tests, run_unverfied_tests: Some(req.run_unverfied_tests), contexts: Some(req.contexts), }, diff --git a/crates/machine-validation/src/lib.rs b/crates/machine-validation/src/lib.rs index 5d83c67b90..2044ae8b77 100644 --- a/crates/machine-validation/src/lib.rs +++ b/crates/machine-validation/src/lib.rs @@ -154,7 +154,8 @@ impl MachineValidationManager { if !machine_validation_filter.allowed_tests.is_empty() && !machine_validation_filter .allowed_tests - .contains(&test.test_id) + .iter() + .any(|t| t.eq_ignore_ascii_case(&test.test_id)) { continue; } diff --git a/crates/machine-validation/src/machine_validation.rs b/crates/machine-validation/src/machine_validation.rs index 42ae1ef9f7..4c1caa18a3 100644 --- a/crates/machine-validation/src/machine_validation.rs +++ b/crates/machine-validation/src/machine_validation.rs @@ -423,7 +423,8 @@ impl MachineValidation { if !machine_validation_filter.allowed_tests.is_empty() && !machine_validation_filter .allowed_tests - .contains(&test.test_id) + .iter() + .any(|t| t.eq_ignore_ascii_case(&test.test_id)) { continue; }