diff --git a/CedarJavaFFI/src/interface.rs b/CedarJavaFFI/src/interface.rs index 4d607e99..9b42a5ff 100644 --- a/CedarJavaFFI/src/interface.rs +++ b/CedarJavaFFI/src/interface.rs @@ -14,33 +14,36 @@ * limitations under the License. */ -use cedar_policy::entities_errors::EntitiesError; +/// +/// +/// Refactors and corresponding tests +/// +/// +use crate::objects::JFormatterConfig; +use crate::{ + answer::Answer, + jmap::Map, + jset::Set, + objects::{JEntityId, JEntityTypeName, JEntityUID, JPolicy, Object}, + utils::raise_npe, +}; #[cfg(feature = "partial-eval")] use cedar_policy::ffi::is_authorized_partial_json_str; +use cedar_policy::{entities_errors::EntitiesError, EntityTypeName}; use cedar_policy::{ ffi::{is_authorized_json_str, validate_json_str}, Entities, EntityUid, Policy, PolicySet, Schema, Template, }; use cedar_policy_formatter::{policies_str_to_pretty, Config}; use jni::{ - objects::{JClass, JObject, JString, JValueGen, JValueOwned}, + objects::{JClass, JObject, JString, JValue, JValueGen, JValueOwned}, sys::{jstring, jvalue}, JNIEnv, }; use jni_fn::jni_fn; use serde::{Deserialize, Serialize}; use serde_json::{from_str, Value}; -use std::{error::Error, str::FromStr, thread}; - -use crate::objects::JFormatterConfig; -use crate::{ - answer::Answer, - jmap::Map, - jset::Set, - objects::{JEntityId, JEntityTypeName, JEntityUID, JPolicy, Object}, - utils::raise_npe, -}; - +use std::{collections::HashMap, error::Error, str::FromStr, thread}; type Result = std::result::Result>; const V0_AUTH_OP: &str = "AuthorizationOperation"; @@ -64,6 +67,13 @@ fn build_err_obj(env: &JNIEnv<'_>, err: &str) -> jstring { fn call_cedar_in_thread(call_str: String, input_str: String) -> String { call_cedar(&call_str, &input_str) } +fn build_err_str(err: &str) -> String { + serde_json::to_string(&Answer::fail_bad_request(vec![format!( + "failed {} java string", + err + )])) + .expect("Could not serialize Answer::Failure") +} /// JNI entry point for authorization and validation requests #[jni_fn("com.cedarpolicy.BasicAuthorizationEngine")] @@ -274,6 +284,22 @@ pub fn parsePoliciesJni<'a>(mut env: JNIEnv<'a>, _: JClass, policies_jstr: JStri } } +pub fn parse_policy_set_to_text_map( + input: &str, +) -> Result<(Vec<(String, String)>, Vec<(String, String)>)> { + let policy_set = PolicySet::from_str(input)?; + let policies = policy_set + .policies() + .map(|p| (p.id().to_string(), p.to_string())) + .collect(); + + let templates = policy_set + .templates() + .map(|t| (t.id().to_string(), t.to_string())) + .collect(); + Ok((policies, templates)) +} + fn parse_policies_internal<'a>( env: &mut JNIEnv<'a>, policies_jstr: JString<'a>, @@ -281,60 +307,51 @@ fn parse_policies_internal<'a>( if policies_jstr.is_null() { raise_npe(env) } else { - // Parse the string into the Rust PolicySet let policies_jstring = env.get_string(&policies_jstr)?; let policies_string = String::from(policies_jstring); - let policy_set = PolicySet::from_str(&policies_string)?; - - // Enumerate over the parsed policies - let mut policies_java_hash_set = Set::new(env)?; - for policy in policy_set.policies() { - let policy_id = format!("{}", policy.id()); - let policy_text = format!("{}", policy); - let java_policy_object = JPolicy::new( - env, - &env.new_string(&policy_text)?, - &env.new_string(&policy_id)?, - )?; - let _ = policies_java_hash_set.add(env, java_policy_object); - } - let mut templates_java_hash_set = Set::new(env)?; - for template in policy_set.templates() { - let policy_id = format!("{}", template.id()); - let policy_text = format!("{}", template); - let java_policy_object = JPolicy::new( - env, - &env.new_string(&policy_text)?, - &env.new_string(&policy_id)?, - )?; - let _ = templates_java_hash_set.add(env, java_policy_object); - } + match parse_policy_set_to_text_map(&policies_string) { + Ok((policies, templates)) => { + let policies_set = build_jpolicy_set(env, policies)?; + let template_set = build_jpolicy_set(env, templates)?; - let java_policy_set = create_java_policy_set( - env, - policies_java_hash_set.as_ref(), - templates_java_hash_set.as_ref(), - ); + let policy_set_obj = + create_java_policy_set(env, policies_set.as_ref(), template_set.as_ref())?; + Ok(JValueGen::Object(policy_set_obj)) + } - Ok(JValueGen::Object(java_policy_set)) + Err(e) => Err(e), + } } } - +fn build_jpolicy_set<'a>( + env: &mut JNIEnv<'a>, + entries: Vec<(String, String)>, +) -> Result>> { + let mut set = Set::new(env)?; + for (id, text) in entries { + let j_policy_id = env.new_string(id)?; + let j_policy_text = env.new_string(text)?; + let policy_obj = JPolicy::new(env, &j_policy_text, &j_policy_id)?; + set.add(env, policy_obj)?; + } + Ok(set) +} fn create_java_policy_set<'a>( env: &mut JNIEnv<'a>, policies_java_hash_set: &JObject<'a>, templates_java_hash_set: &JObject<'a>, -) -> JObject<'a> { - env.new_object( +) -> Result> { + let policy_set_obj = env.new_object( "com/cedarpolicy/model/policy/PolicySet", "(Ljava/util/Set;Ljava/util/Set;)V", &[ JValueGen::Object(policies_java_hash_set), JValueGen::Object(templates_java_hash_set), ], - ) - .expect("Failed to create new PolicySet object") + )?; + + Ok(policy_set_obj) } #[jni_fn("com.cedarpolicy.model.policy.Policy")] @@ -549,18 +566,22 @@ pub fn getEntityIdentifierRepr<'a>(mut env: JNIEnv<'a>, _: JClass, obj: JObject< } } +fn cedar_escape_string(input: &str) -> String { + input.replace('\\', "\\\\").replace('"', "\\\"") +} +// added javlue gen fn get_entity_identifier_repr_internal<'a>( env: &mut JNIEnv<'a>, obj: JObject<'a>, ) -> Result> { if obj.is_null() { - raise_npe(env) - } else { - let eid = JEntityId::cast(env, obj)?; - let repr = eid.get_string_repr(); - let jstring = env.new_string(repr)?.into(); - Ok(JValueGen::Object(jstring)) + return raise_npe(env); } + let jstr = JString::from(obj); + let rust_str: String = env.get_string(&jstr)?.into(); + let cedar_style = format!("\"{}\"", cedar_escape_string(&rust_str)); + + Ok(JValueGen::Object(env.new_string(cedar_style)?.into())) } #[jni_fn("com.cedarpolicy.value.EntityTypeName")] @@ -571,17 +592,20 @@ pub fn parseEntityTypeName<'a>(mut env: JNIEnv<'a>, _: JClass, obj: JString<'a>) } } +fn parse_entity_type_name_str(input: &str) -> Result { + Ok(EntityTypeName::from_str(input)?) +} pub fn parse_entity_type_name_internal<'a>( env: &mut JNIEnv<'a>, obj: JString<'a>, ) -> Result>> { if obj.is_null() { - raise_npe(env) - } else { - let jstring = env.get_string(&obj)?; - let src = String::from(jstring); - JEntityTypeName::parse(env, &src).map(Into::into) + return raise_npe(env); } + let input_str: String = env.get_string(&obj)?.into(); + let entity_type = parse_entity_type_name_str(&input_str)?; + let j_entity_type_name = JEntityTypeName::try_from(env, &entity_type)?; + Ok(j_entity_type_name.into()) } #[jni_fn("com.cedarpolicy.value.EntityTypeName")] @@ -597,12 +621,11 @@ fn get_entity_type_name_repr_internal<'a>( obj: JObject<'a>, ) -> Result> { if obj.is_null() { - raise_npe(env) - } else { - let etype = JEntityTypeName::cast(env, obj)?; - let repr = etype.get_string_repr(); - Ok(env.new_string(repr)?.into()) + return raise_npe(env); } + let jstr = JString::from(obj); + let rust_str: String = env.get_string(&jstr)?.into(); + Ok(env.new_string(rust_str)?.into()) } #[jni_fn("com.cedarpolicy.value.EntityUID")] @@ -613,19 +636,41 @@ pub fn parseEntityUID<'a>(mut env: JNIEnv<'a>, _: JClass, obj: JString<'a>) -> j }; r } +pub fn entity_uid_str(euid_str: &str) -> Result> { + let cedar_euid = EntityUid::from_str(euid_str)?; + let mut result = HashMap::new(); + result.insert("id".to_string(), format!("{:?}", cedar_euid.id())); + result.insert("type".to_string(), cedar_euid.type_name().to_string()); + Ok(result) +} fn parse_entity_uid_internal<'a>( env: &mut JNIEnv<'a>, obj: JString<'a>, ) -> Result> { if obj.is_null() { - raise_npe(env) - } else { - let jstring = env.get_string(&obj)?; - let src = String::from(jstring); - let obj = JEntityUID::parse(env, &src)?; - Ok(obj.into()) + return raise_npe(env); + } + + let jstring = env.get_string(&obj)?; + let src = jstring.to_str()?; + let parsed_result_map = entity_uid_str(src)?; + let map_obj = env.new_object("java/util/HashMap", "()V", &[])?; + for (key, value) in parsed_result_map { + let j_key_string = env.new_string(key)?; + let j_value_string = env.new_string(value)?; + env.call_method( + &map_obj, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + &[ + JValueGen::Object(&j_key_string), + JValueGen::Object(&j_value_string), + ], + )?; } + + Ok(JValueOwned::Object(map_obj)) } #[jni_fn("com.cedarpolicy.value.EntityUID")] @@ -644,18 +689,29 @@ pub fn getEUIDRepr<'a>( fn get_euid_repr_internal<'a>( env: &mut JNIEnv<'a>, - type_name: JObject<'a>, - id: JObject<'a>, + type_name_obj: JObject<'a>, + id_obj: JObject<'a>, ) -> Result> { - if type_name.is_null() || id.is_null() { - raise_npe(env) - } else { - let etype = JEntityTypeName::cast(env, type_name)?.get_rust_repr(); - let id = JEntityId::cast(env, id)?.get_rust_repr(); - let euid = EntityUid::from_type_name_and_id(etype, id); - let jstring = env.new_string(euid.to_string())?; - Ok(jstring.into()) + if type_name_obj.is_null() || id_obj.is_null() { + return raise_npe(env); } + + // Extract string from EntityTypeName object + let type_name_result = + env.call_method(&type_name_obj, "toString", "()Ljava/lang/String;", &[])?; + let type_jstr = JString::from(type_name_result.l()?); + let type_name: String = env.get_string(&type_jstr)?.into(); + + // Extract string from EntityIdentifier object + let id_result = env.call_method(&id_obj, "toString", "()Ljava/lang/String;", &[])?; + let id_jstr = JString::from(id_result.l()?); + let id: String = env.get_string(&id_jstr)?.into(); + + // Format into expected EntityUid string format + let euid_string = format!(r#"{type}::"{id}""#, type = type_name, id = id); + + // Return as Java JString + Ok(env.new_string(euid_string)?.into()) } #[jni_fn("com.cedarpolicy.formatter.PolicyFormatter")] @@ -683,6 +739,10 @@ pub fn policiesStrToPrettyWithConfig<'a>( } } +fn format_policies_str_with_config(input: &str, config: &Config) -> Result { + policies_str_to_pretty(input, config).map_err(|e| Box::::from(e.to_string())) +} + fn policies_str_to_pretty_internal<'a>( env: &mut JNIEnv<'a>, policies_jstr: JString<'a>, @@ -697,13 +757,10 @@ fn policies_str_to_pretty_internal<'a>( Config::default() }; let policies_str = String::from(env.get_string(&policies_jstr)?); - match policies_str_to_pretty(&policies_str, &config) { - Ok(formatted_policies) => Ok(env.new_string(formatted_policies)?.into()), - Err(e) => Err(e.into()), - } + let formatted_string = format_policies_str_with_config(&policies_str, &config)?; + Ok(env.new_string(formatted_string)?.into()) } } - #[cfg(test)] mod jvm_based_tests { use super::*; @@ -711,10 +768,13 @@ mod jvm_based_tests { use jni::JavaVM; use std::sync::LazyLock; - // Static JVM to be used by all the tests. LazyLock for thread-safe lazy initialization static JVM: LazyLock = LazyLock::new(|| create_jvm().unwrap()); mod policy_tests { + use std::result; + + use cedar_policy::Effect; + use super::*; #[track_caller] @@ -732,6 +792,17 @@ mod jvm_based_tests { policy_effect_test_util(&mut env, "permit(principal,action,resource);", "permit"); policy_effect_test_util(&mut env, "forbid(principal,action,resource);", "forbid"); } + #[test] + fn policy_effect_jni_internal_null() { + let mut env = JVM.attach_current_thread().unwrap(); + let null_obj = JObject::null(); + let result = policy_effect_jni_internal(&mut env, null_obj.into()); + assert!(result.is_ok(), "Expected error on null input"); + assert!( + env.exception_check().unwrap(), + "Expected Java exception due to a null input" + ); + } #[track_caller] fn assert_id_annotation_eq( @@ -802,8 +873,47 @@ mod jvm_based_tests { "myAnnotatedValue", ); } - } + #[test] + fn get_template_annotations_internal_null() { + let mut env = JVM.attach_current_thread().unwrap(); + let null_obj = JObject::null(); + let result = get_template_annotations_internal(&mut env, null_obj.into()); + assert!(result.is_ok(), "Expected error on null input"); + assert!( + env.exception_check().unwrap(), + "Expected java exception due to a null input" + ); + } + #[test] + fn get_policy_annotations_internal_null() { + let mut env = JVM.attach_current_thread().unwrap(); + let null_obj = JObject::null(); + let result = get_policy_annotations_internal(&mut env, null_obj.into()); + assert!(result.is_ok(), "Expected error on null input"); + assert!( + env.exception_check().unwrap(), + "Expected java exception due to a null input" + ); + } + #[test] + fn parse_policies_internal_invalid() { + let mut env = JVM.attach_current_thread().unwrap(); + + let invalid_input = "not a valid input"; + let policy_jstr = env.new_string(invalid_input).unwrap(); + + let result = parse_policies_internal(&mut env, policy_jstr); + assert!(result.is_err(), "Expected to fail or invalid input"); + } + #[test] + fn parse_policy_internal_null() { + let mut env = JVM.attach_current_thread().unwrap(); + let null_str = JString::from(JObject::null()); + let result = parse_policy_internal(&mut env, null_str); + assert!(result.is_ok(), "Expected error on null input"); + } + } mod map_tests { use super::*; @@ -871,4 +981,475 @@ mod jvm_based_tests { ) } } + mod schema_test { + use std::result; + + use super::*; + use cedar_policy::{EntityId, Schema}; + + #[test] + fn parse_policies_set_text_map_valid() { + let input_policies = r#"permit(principal, action , resource); + permit(principal,action,resource) when {principal has x && principal.x == 5};"#; + + let result = parse_policy_set_to_text_map(input_policies); + + assert!(result.is_ok()) + } + + #[test] + fn parse_policies_set_map_invalid() { + let invalid_policies = r#"permit(principal?, action? , resource); + permit(principal,action,resource) if {principal has x && principal.x == 5};"#; + + let result = parse_policy_set_to_text_map(invalid_policies); + assert!(result.is_err(), "Expected policy set to fail") + } + #[test] + fn parse_policies_empty() { + let input = ""; + let result = parse_policy_set_to_text_map(input).unwrap(); + + let (policies, templates) = result; + assert!(policies.is_empty()); + assert!(templates.is_empty()) + } + #[test] + fn parse_policies_internal_null() { + let mut env = JVM.attach_current_thread().unwrap(); + let null_str = JString::from(JObject::null()); + let result = parse_policies_internal(&mut env, null_str); + assert!(result.is_ok(), "Expected error on null input"); + } + #[test] + fn parse_policy_set_to_text_map_static_policies() { + let input = r#" + permit(principal,action,resource); + forbid(principal == User::"bob",action,resource);"#; + let result = parse_policy_set_to_text_map(input).unwrap(); + + let (policies, templates) = result; + assert_eq!(policies.len(), 2); + + let p1_found = policies + .iter() + .any(|(id, src)| src.contains("permit") && id.starts_with("policy")); + let p2_found = policies.iter().any(|(id, src)| { + src.contains("forbid") && src.contains("User::\"bob\"") && id.starts_with("policy") + }); + + assert!(p1_found); + assert!(p2_found); + + assert!(templates.is_empty()); + } + #[test] + fn parse_policy_set_to_text_map_templates() { + let input = r#" + permit(principal,action,resource); + "#; + + let result = parse_policy_set_to_text_map(input).unwrap(); + let (policies, templates) = result; + + assert_eq!(policies.len(), 1, "Expected 1 static policy."); + assert!(templates.is_empty(), "Expected no templates."); + + let p1_found = policies.iter().any(|(_id, src)| src.contains("permit")); + assert!(p1_found, "permit policy not found or content is incorrect."); + } + + #[test] + fn get_entity_identifier_repr_internal_null_input() { + let mut env = JVM.attach_current_thread().unwrap(); + let result = get_entity_identifier_repr_internal(&mut env, JObject::null()); + assert!(env.exception_check().unwrap()); + assert!( + result.is_ok(), + "Expected get_entity_identifier_repr_internal to succeed" + ); + } + //mock test + #[test] + fn get_entity_identifier_repr_internal_raw_string_mock_valid() { + let mut env = JVM.attach_current_thread().unwrap(); + let input = env.new_string("alice").unwrap(); + + let result_jval = + get_entity_identifier_repr_internal(&mut env, JObject::from(input)).unwrap(); + let output_jstring = JString::from(result_jval.l().unwrap()); + let output_str: String = env.get_string(&output_jstring).unwrap().into(); + + assert_eq!(output_str, r#""alice""#); + } + #[test] + fn get_entity_identifier_repr_internal_valid_entity_obj() { + let mut env = JVM.attach_current_thread().unwrap(); + let input = env.new_string("alice").unwrap(); + + let result_jval = + get_entity_identifier_repr_internal(&mut env, JObject::from(input)).unwrap(); + let output_jstring = JString::from(result_jval.l().unwrap()); + let output_str: String = env.get_string(&output_jstring).unwrap().into(); + + assert_eq!(output_str, r#""alice""#); + } + + #[test] + fn get_entity_identifier_repr_internal_with_escape() { + let mut env = JVM.attach_current_thread().unwrap(); + let input = env.new_string(r#"bob\smith"#).unwrap(); + + let result_jval = + get_entity_identifier_repr_internal(&mut env, JObject::from(input)).unwrap(); + let output_jstring = JString::from(result_jval.l().unwrap()); + let output_str: String = env.get_string(&output_jstring).unwrap().into(); + + assert_eq!(output_str, r#""bob\\smith""#); + } + + #[cfg(test)] + mod entity_tests { + use super::*; + use cedar_policy::EntityId; + use std::result; + + #[test] + fn parse_entity_type_name_internal_test() { + let input = "User"; + let result = parse_entity_type_name_str(input).unwrap(); + assert_eq!(result.basename(), "User"); + assert!(result.namespace_components().next().is_none()); + assert_eq!(result.to_string(), "User") + } + #[test] + fn parse_entity_type_name_internal_null_input() { + let mut env = JVM.attach_current_thread().unwrap(); + let null_input = JString::from(JObject::null()); + + let result = parse_entity_type_name_internal(&mut env, null_input); + + assert!(result.is_ok(), "Expected Ok result for null input"); + assert!( + env.exception_check().unwrap(), + "Expected Java exception due to null input" + ); + } + #[test] + fn parse_entity_type_name_internal_invalid() { + let mut env = JVM.attach_current_thread().unwrap(); + let input_jstr = env.new_string("Invalid::Type!").unwrap(); + + let result = parse_entity_type_name_internal(&mut env, input_jstr); + assert!( + result.is_err(), + "Expected error for invalid entity type name input" + ); + } + + #[test] + fn parse_entity_type_name_valid_namespacestr() { + let input = "PhotoApp::UserGroup::Admin"; + let result = parse_entity_type_name_str(input).unwrap(); + assert_eq!(result.basename(), "Admin"); + let namespace: Vec<&str> = result.namespace_components().collect(); + assert_eq!(namespace, vec!["PhotoApp", "UserGroup"]); + assert_eq!(result.to_string(), "PhotoApp::UserGroup::Admin"); + } + #[test] + fn parse_entity_type_name_emptystr() { + let input = ""; + let result = parse_entity_type_name_str(input); + assert!( + result.is_err(), + "Expected parsing failed due to an empty string" + ); + } + #[test] + fn parse_entity_type_name_separators_str() { + let input = "::"; + let result = parse_entity_type_name_str(input); + assert!( + result.is_err(), + "Expected parsing failed for only separators" + ); + } + #[test] + fn simple_entity_type_valid() { + let input = "User"; + let result = parse_entity_type_name_str(input).unwrap(); + assert_eq!(result.basename().to_string(), "User"); + assert!(result.namespace().is_empty(), "Expected no namespace"); + } + #[test] + fn valid_policy_formats_successfully() { + let input = r#" + permit(principal, action, resource); + "#; + let config = Config::default(); + + let result = format_policies_str_with_config(input, &config); + assert!(result.is_ok(), "Expected formatting to succeed"); + + let output = result.unwrap(); + assert!( + output.contains("permit"), + "Expected output to include 'permit'" + ); + } + #[test] + fn invalid_policy_returns_error() { + let input = r#" + permit(principal,, action, resource); + "#; + let config = Config::default(); + + let result = format_policies_str_with_config(input, &config); + assert!( + result.is_err(), + "Expected formatting to fail due to syntax error" + ); + } + #[test] + fn format_policies_str_with_config_failures() { + let invalid_policies = [ + "permit(principal,, action, resource);", + "invalid syntax here", + "permit(principal action resource", + ]; + + let config = cedar_policy_formatter::Config::default(); + for invalid_input in invalid_policies { + let result = format_policies_str_with_config(invalid_input, &config); + assert!(result.is_err(), "Expected error for: {}", invalid_input); + } + } + #[test] + fn policies_str_to_pretty_null() { + let mut env = JVM.attach_current_thread().unwrap(); + let null_str = JString::from(JObject::null()); + let result = policies_str_to_pretty_internal(&mut env, null_str, None); + assert!(result.is_ok()); + } + + #[test] + fn policies_str_to_pretty_internal_valid_policy_string() { + let mut env = JVM.attach_current_thread().unwrap(); + + let input = r#"permit(principal, action, resource);"#; + let policies_jstr = env.new_string(input).unwrap(); + + let result = policies_str_to_pretty_internal(&mut env, policies_jstr, None); + assert!( + result.is_ok(), + "Expected valid policy string to format successfully, got: {:?}", + result + ); + + let formatted_jvalue = result.unwrap(); + let jstring_obj: JString = formatted_jvalue.l().unwrap().into(); + let formatted_str: String = env.get_string(&jstring_obj).unwrap().into(); + + assert!( + formatted_str.contains("permit"), + "Expected output to contain 'permit'." + ); + assert!( + formatted_str.contains("(") && formatted_str.contains(")"), + "Expected parentheses in formatted output." + ); + } + #[test] + fn parse_entity_uid_internal_str_failures() { + let invalid_cases = [ + "invalid::syntax", + "::empty", + "no_colon_separator", + "User::missing_quotes", + ]; + + for invalid_input in invalid_cases { + let result = entity_uid_str(invalid_input); + assert!(result.is_err(), "Expected error for: {}", invalid_input); + } + } + + #[test] + fn parse_entity_type_name_str_valid_simple() { + let input = "User"; + let result = parse_entity_type_name_str(input); + assert!(result.is_ok(), "Expected valid parse for 'User'."); + let parsed_etype = result.unwrap(); + assert_eq!(parsed_etype.basename(), "User"); + assert!(parsed_etype.namespace_components().next().is_none()); + } + #[test] + fn parse_entity_type_name_str_failures() { + let invalid_cases = ["", "::", "Invalid::Type!", "123InvalidStart"]; + + for invalid_input in invalid_cases { + let result = parse_entity_type_name_str(invalid_input); + assert!(result.is_err(), "Expected error for: {}", invalid_input); + } + } + + #[test] + fn build_java_policy_set_empty() { + let mut env = JVM.attach_current_thread().unwrap(); + let entries: Vec<(String, String)> = vec![]; + + let result = build_jpolicy_set(&mut env, entries); + assert!(result.is_ok()); + } + } + #[cfg(test)] + mod entity_type_tests { + use super::*; + use crate::{ + jlist::List, + objects::{JEntityId, JEntityTypeName}, + }; + + #[test] + fn get_euid_repr_internal_from_strings() { + let mut env = JVM.attach_current_thread().unwrap(); + + let type_str = env.new_string("User").unwrap(); + let id_str = env.new_string("alice").unwrap(); + + let result = get_euid_repr_internal(&mut env, type_str.into(), id_str.into()); + + assert!(result.is_ok(), "Expected success"); + + let jval = result.unwrap(); + let output_jstr = JString::from(jval.l().unwrap()); + let output: String = env.get_string(&output_jstr).unwrap().into(); + + assert_eq!(output, r#"User::"alice""#); + } + #[test] + fn get_euid_repr_internal_empty_strings() { + let mut env = JVM.attach_current_thread().unwrap(); + + let type_str = env.new_string("").unwrap(); + let id_str = env.new_string("").unwrap(); + + let result = get_euid_repr_internal(&mut env, type_str.into(), id_str.into()); + assert!(result.is_ok()); + + let jval = result.unwrap(); + let output_jstr = JString::from(jval.l().unwrap()); + let output: String = env.get_string(&output_jstr).unwrap().into(); + + assert_eq!(output, r#"::"""#); + } + #[test] + fn get_euid_repr_internal_null() { + let mut env = JVM.attach_current_thread().unwrap(); + + let null_type = JObject::null(); + let null_id = JObject::null(); + let result = get_euid_repr_internal(&mut env, null_type, null_id); + assert!( + result.is_ok(), + "Expected error when both arguments are null" + ); + } + #[test] + fn get_euid_repr_internal_special_chars() { + let mut env = JVM.attach_current_thread().unwrap(); + + let type_str = env.new_string("Some::Type").unwrap(); + let id_str = env.new_string("id-with-specials!@#$%^&*()").unwrap(); + + let result = get_euid_repr_internal(&mut env, type_str.into(), id_str.into()); + assert!(result.is_ok()); + + let jval = result.unwrap(); + let output_jstr = JString::from(jval.l().unwrap()); + let output: String = env.get_string(&output_jstr).unwrap().into(); + + assert_eq!(output, r#"Some::Type::"id-with-specials!@#$%^&*()""#); + } + + #[test] + fn get_entity_type_name_valid() { + let mut env = JVM.attach_current_thread().unwrap(); + let input = env.new_string("User").unwrap(); + + let result = get_entity_type_name_repr_internal(&mut env, input.into()); + + assert!(result.is_ok()); + + let output_jval = result.unwrap(); + let output_jstr = JString::from(output_jval.l().unwrap()); + let output: String = env.get_string(&output_jstr).unwrap().into(); + assert_eq!(output, "User"); + } + + #[test] + fn get_entity_type_name_repr_null() { + let mut env = JVM.attach_current_thread().unwrap(); + let null_obj = JObject::null(); + let result = get_entity_type_name_repr_internal(&mut env, null_obj); + assert!( + result.is_ok(), + "Expected get_entity_type_name to fail for null input" + ); + } + mod policy_set_tests { + use super::*; + use crate::interface::parse_policy_set_to_text_map; + + #[test] + fn parse_policy_set_to_text_map_empty() { + let input = ""; + let result = parse_policy_set_to_text_map(input); + assert!(result.is_ok(), "Failed to parse empty policy set"); + let (policies, templates) = result.unwrap(); + assert!(policies.is_empty()); + assert!(templates.is_empty()); + } + + #[test] + fn parse_policy_set_to_text_map_with_policy() { + let input = r#"permit(principal, action, resource);"#; + let result = parse_policy_set_to_text_map(input); + assert!(result.is_ok(), "Failed to parse policy set with one policy"); + let (policies, templates) = result.unwrap(); + assert_eq!(policies.len(), 1); + assert!(templates.is_empty()); + } + + #[test] + fn parse_policy_set_to_text_map_with_template() { + let input = r#"permit(principal == ?principal, action, resource);"#; + let result = parse_policy_set_to_text_map(input); + assert!(result.is_ok(), "Failed to parse policy set with template"); + let (policies, templates) = result.unwrap(); + assert_eq!(templates.len(), 1); + assert!(policies.is_empty()); + } + + #[test] + fn parse_policy_set_to_text_map_invalid() { + let input = "invalid policy syntax"; + let result = parse_policy_set_to_text_map(input); + assert!(result.is_err(), "Should fail on invalid policy syntax"); + } + + #[test] + fn create_java_policy_set_with_mock_sets() { + let mut env = JVM.attach_current_thread().unwrap(); + let policies_set = env.new_object("java/util/HashSet", "()V", &[]).unwrap(); + let templates_set = env.new_object("java/util/HashSet", "()V", &[]).unwrap(); + let result = create_java_policy_set(&mut env, &policies_set, &templates_set); + assert!( + result.is_err(), + "Expected error due to missing PolicySet class" + ); + } + } + } + } }