2026-03-31 11:02:59 +00:00
use crate ::api_token ::APIToken ;
2026-05-12 18:31:08 +00:00
use axum ::Json ;
2026-05-18 14:26:51 +00:00
use log ::{ debug , error , info , warn } ;
2026-02-15 17:11:57 +00:00
use serde ::Serialize ;
2026-03-31 11:02:59 +00:00
use std ::collections ::{ HashMap , HashSet } ;
use std ::env ;
use std ::fs ;
use std ::path ::{ Path , PathBuf } ;
use std ::sync ::OnceLock ;
2025-04-03 12:25:45 +00:00
use sys_locale ::get_locale ;
2024-11-05 20:39:21 +00:00
2026-02-16 11:13:36 +00:00
const DEFAULT_LANGUAGE : & str = " en-US " ;
2026-05-31 10:11:09 +00:00
const ENTERPRISE_CONFIG_SLOT_MAX : u32 = 99_999 ;
const ENTERPRISE_CONFIG_SLOT_WIDTH : usize = 5 ;
const ENTERPRISE_CONFIG_ID_KEY_PREFIX : & str = " config_id " ;
const ENTERPRISE_CONFIG_SERVER_URL_KEY_PREFIX : & str = " config_server_url " ;
2026-03-31 11:02:59 +00:00
#[ cfg(target_os = " windows " ) ]
const ENTERPRISE_REGISTRY_KEY_PATH : & str = r "Software\github\MindWork AI Studio\Enterprise IT" ;
const ENTERPRISE_POLICY_SECRET_FILE_NAME : & str = " config_encryption_secret.yaml " ;
2026-06-11 09:37:40 +00:00
const EXTERNAL_HTTP_CUSTOM_ROOT_CERTIFICATE_POLICY_FILE_NAME : & str = " external_http_custom_root_certificates.yaml " ;
pub const DOTNET_ENV_CUSTOM_ROOT_CERTIFICATE_POLICY_CONFIGURED : & str = " AI_STUDIO_EXTERNAL_HTTP_CUSTOM_ROOT_CERTIFICATES_POLICY_CONFIGURED " ;
pub const DOTNET_ENV_CUSTOM_ROOT_CERTIFICATES_ENABLED : & str = " AI_STUDIO_EXTERNAL_HTTP_CUSTOM_ROOT_CERTIFICATES_ENABLED " ;
pub const DOTNET_ENV_CUSTOM_ROOT_CERTIFICATE_BUNDLE_PATH : & str = " AI_STUDIO_EXTERNAL_HTTP_CUSTOM_ROOT_CERTIFICATE_BUNDLE_PATH " ;
pub const DOTNET_ENV_CUSTOM_ROOT_CERTIFICATE_ALLOWED_HOSTS : & str = " AI_STUDIO_EXTERNAL_HTTP_CUSTOM_ROOT_CERTIFICATE_ALLOWED_HOSTS " ;
2026-03-31 11:02:59 +00:00
2026-05-31 17:24:11 +00:00
#[ cfg(any(target_os = " linux " , test)) ]
const FLATPAK_ENTERPRISE_POLICY_DIRECTORY : & str = " /app/etc/MindWorkAI " ;
2026-05-31 10:11:09 +00:00
const ENTERPRISE_ENV_CONFIG_ID_PREFIX : & str = " MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID " ;
const ENTERPRISE_ENV_CONFIG_SERVER_URL_PREFIX : & str = " MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_SERVER_URL " ;
const ENTERPRISE_ENV_CONFIGS : & str = " MINDWORK_AI_STUDIO_ENTERPRISE_CONFIGS " ;
const ENTERPRISE_ENV_CONFIG_ENCRYPTION_SECRET : & str = " MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ENCRYPTION_SECRET " ;
2024-11-05 20:39:21 +00:00
/// The data directory where the application stores its data.
pub static DATA_DIRECTORY : OnceLock < String > = OnceLock ::new ( ) ;
/// The config directory where the application stores its configuration.
pub static CONFIG_DIRECTORY : OnceLock < String > = OnceLock ::new ( ) ;
2026-02-25 18:30:46 +00:00
/// The user language cached once per runtime process.
static USER_LANGUAGE : OnceLock < String > = OnceLock ::new ( ) ;
2024-11-05 20:39:21 +00:00
/// Returns the config directory.
2026-05-12 18:31:08 +00:00
pub async fn get_config_directory ( _token : APIToken ) -> String {
2024-11-05 20:39:21 +00:00
match CONFIG_DIRECTORY . get ( ) {
Some ( config_directory ) = > config_directory . clone ( ) ,
None = > String ::from ( " " ) ,
}
}
/// Returns the data directory.
2026-05-12 18:31:08 +00:00
pub async fn get_data_directory ( _token : APIToken ) -> String {
2024-11-05 20:39:21 +00:00
match DATA_DIRECTORY . get ( ) {
Some ( data_directory ) = > data_directory . clone ( ) ,
None = > String ::from ( " " ) ,
}
}
2026-05-18 14:26:51 +00:00
/// Returns the current user's username.
pub async fn read_user_name ( _token : APIToken ) -> String {
whoami ::username ( ) . unwrap_or_else ( | e | {
error! ( " Failed to read the current OS username: {e}. " ) ;
String ::new ( )
} )
}
2026-05-31 17:10:19 +00:00
#[ derive(Clone, Debug, PartialEq, Eq, Serialize) ]
pub struct RuntimeInfo {
pub working_directory : String ,
pub executable_path : String ,
pub linux_package_type : String ,
}
pub async fn get_runtime_info ( _token : APIToken ) -> Json < RuntimeInfo > {
Json ( RuntimeInfo {
working_directory : env ::current_dir ( )
. map ( | path | path . to_string_lossy ( ) . into_owned ( ) )
. unwrap_or_default ( ) ,
executable_path : env ::current_exe ( )
. map ( | path | path . to_string_lossy ( ) . into_owned ( ) )
. unwrap_or_default ( ) ,
linux_package_type : detect_linux_package_type ( ) . to_string ( ) ,
} )
}
#[ cfg(target_os = " linux " ) ]
fn detect_linux_package_type ( ) -> & 'static str {
if is_flatpak ( ) {
" flatpak "
} else if is_appimage ( ) {
" appimage "
} else {
" unknown "
}
}
#[ cfg(not(target_os = " linux " )) ]
fn detect_linux_package_type ( ) -> & 'static str {
" not_applicable "
}
#[ cfg(target_os = " linux " ) ]
fn is_flatpak ( ) -> bool {
env_var_has_value ( " FLATPAK_ID " )
| | Path ::new ( " /.flatpak-info " ) . is_file ( )
| | env ::var ( " container " )
. is_ok_and ( | value | value . trim ( ) . eq_ignore_ascii_case ( " flatpak " ) )
}
#[ cfg(target_os = " linux " ) ]
fn is_appimage ( ) -> bool {
env_var_has_value ( " APPIMAGE " ) | | env_var_has_value ( " APPDIR " )
}
#[ cfg(target_os = " linux " ) ]
fn env_var_has_value ( key : & str ) -> bool {
env ::var ( key ) . is_ok_and ( | value | ! value . trim ( ) . is_empty ( ) )
}
2024-11-05 20:39:21 +00:00
/// Returns true if the application is running in development mode.
pub fn is_dev ( ) -> bool {
cfg! ( debug_assertions )
}
/// Returns true if the application is running in production mode.
pub fn is_prod ( ) -> bool {
! is_dev ( )
2025-04-03 12:25:45 +00:00
}
2026-02-16 11:13:36 +00:00
fn normalize_locale_tag ( locale : & str ) -> Option < String > {
let trimmed = locale . trim ( ) ;
if trimmed . is_empty ( ) {
return None ;
}
let without_encoding = trimmed
. split ( '.' )
. next ( )
. unwrap_or ( trimmed )
. split ( '@' )
. next ( )
. unwrap_or ( trimmed )
. trim ( ) ;
if without_encoding . is_empty ( ) {
return None ;
}
let normalized_delimiters = without_encoding . replace ( '_' , " - " ) ;
let mut segments = normalized_delimiters
. split ( '-' )
. filter ( | segment | ! segment . is_empty ( ) ) ;
let language = segments . next ( ) ? ;
if language . eq_ignore_ascii_case ( " c " ) | | language . eq_ignore_ascii_case ( " posix " ) {
return None ;
}
let language = language . to_ascii_lowercase ( ) ;
if language . len ( ) < 2 | | ! language . chars ( ) . all ( | c | c . is_ascii_alphabetic ( ) ) {
return None ;
}
2026-05-16 16:53:53 +00:00
if let Some ( region ) = segments . next ( ) & & region . len ( ) = = 2 & & region . chars ( ) . all ( | c | c . is_ascii_alphabetic ( ) ) {
return Some ( format! ( " {} - {} " , language , region . to_ascii_uppercase ( ) ) ) ;
2026-02-16 11:13:36 +00:00
}
Some ( language )
}
#[ cfg(target_os = " linux " ) ]
2026-02-25 18:30:46 +00:00
fn read_locale_from_environment ( ) -> Option < ( String , & 'static str ) > {
2026-02-16 11:13:36 +00:00
if let Ok ( language ) = env ::var ( " LANGUAGE " ) {
for candidate in language . split ( ':' ) {
if let Some ( locale ) = normalize_locale_tag ( candidate ) {
2026-02-25 18:30:46 +00:00
return Some ( ( locale , " LANGUAGE " ) ) ;
2026-02-16 11:13:36 +00:00
}
}
}
for key in [ " LC_ALL " , " LC_MESSAGES " , " LANG " ] {
if let Ok ( value ) = env ::var ( key ) {
if let Some ( locale ) = normalize_locale_tag ( & value ) {
2026-02-25 18:30:46 +00:00
return Some ( ( locale , key ) ) ;
2026-02-16 11:13:36 +00:00
}
}
}
None
}
#[ cfg(not(target_os = " linux " )) ]
2026-02-25 18:30:46 +00:00
fn read_locale_from_environment ( ) -> Option < ( String , & 'static str ) > {
2026-02-16 11:13:36 +00:00
None
}
2026-02-25 18:30:46 +00:00
enum LanguageDetectionSource {
SysLocale ,
LinuxEnvironmentVariable ( & 'static str ) ,
DefaultLanguage ,
}
fn detect_user_language ( ) -> ( String , LanguageDetectionSource ) {
if let Some ( locale ) = get_locale ( ) {
if let Some ( normalized_locale ) = normalize_locale_tag ( & locale ) {
return ( normalized_locale , LanguageDetectionSource ::SysLocale ) ;
}
warn! ( " sys-locale returned an unusable locale value: '{}'. " , locale ) ;
}
if let Some ( ( locale , key ) ) = read_locale_from_environment ( ) {
return ( locale , LanguageDetectionSource ::LinuxEnvironmentVariable ( key ) ) ;
}
(
String ::from ( DEFAULT_LANGUAGE ) ,
LanguageDetectionSource ::DefaultLanguage ,
)
}
2026-05-12 18:31:08 +00:00
pub async fn read_user_language ( _token : APIToken ) -> String {
2026-02-25 18:30:46 +00:00
USER_LANGUAGE
. get_or_init ( | | {
let ( user_language , source ) = detect_user_language ( ) ;
match source {
LanguageDetectionSource ::SysLocale = > {
info! ( " Detected user language from sys-locale: '{}'. " , user_language ) ;
} ,
LanguageDetectionSource ::LinuxEnvironmentVariable ( key ) = > {
info! (
" Detected user language from Linux environment variable '{}': '{}'. " ,
key , user_language
) ;
} ,
LanguageDetectionSource ::DefaultLanguage = > {
warn! (
" Could not determine the system language. Use default '{}'. " ,
DEFAULT_LANGUAGE
) ;
} ,
}
2026-02-16 11:13:36 +00:00
2026-02-25 18:30:46 +00:00
user_language
} )
. clone ( )
2025-06-01 19:14:21 +00:00
}
2026-03-31 11:02:59 +00:00
/// Represents a single enterprise configuration entry with an ID and server URL.
#[ derive(Clone, Debug, PartialEq, Eq, Serialize) ]
pub struct EnterpriseConfig {
pub id : String ,
pub server_url : String ,
2026-05-31 10:11:09 +00:00
pub source : String ,
pub source_detail : String ,
pub slot : String ,
}
2026-06-11 09:37:40 +00:00
#[ derive(Clone, Debug, Default, PartialEq, Eq) ]
pub struct ExternalHttpCustomRootCertificatePolicy {
pub is_configured : bool ,
pub enabled : bool ,
pub bundle_path : String ,
pub allowed_hosts : String ,
pub source_detail : String ,
}
2026-05-31 10:11:09 +00:00
#[ derive(Clone, Debug, PartialEq, Eq) ]
struct EnterpriseSourceValue {
value : String ,
source_detail : String ,
}
impl EnterpriseSourceValue {
fn new ( value : String , source_detail : String ) -> Self {
Self {
value ,
source_detail ,
}
}
}
trait EnterpriseSourceValueAccess {
fn value ( & self ) -> & str ;
fn source_detail ( & self ) -> & str ;
}
impl EnterpriseSourceValueAccess for EnterpriseSourceValue {
fn value ( & self ) -> & str {
& self . value
}
fn source_detail ( & self ) -> & str {
& self . source_detail
}
2026-03-31 11:02:59 +00:00
}
2026-05-31 10:11:09 +00:00
impl EnterpriseSourceValueAccess for String {
fn value ( & self ) -> & str {
self
}
fn source_detail ( & self ) -> & str {
" "
}
}
type EnterpriseSourceValues = HashMap < String , EnterpriseSourceValue > ;
2026-03-31 11:02:59 +00:00
#[ derive(Clone, Debug, Default, PartialEq, Eq) ]
struct EnterpriseSourceData {
source_name : String ,
configs : Vec < EnterpriseConfig > ,
encryption_secret : String ,
}
2026-05-12 18:31:08 +00:00
pub async fn read_enterprise_env_config_id ( _token : APIToken ) -> String {
2026-03-31 11:02:59 +00:00
debug! ( " Trying to read the effective enterprise configuration ID. " ) ;
resolve_effective_enterprise_config_source ( )
. configs
. into_iter ( )
. next ( )
. map ( | config | config . id )
. unwrap_or_default ( )
2025-06-01 19:14:21 +00:00
}
2026-05-12 18:31:08 +00:00
pub async fn read_enterprise_env_config_server_url ( _token : APIToken ) -> String {
2026-03-31 11:02:59 +00:00
debug! ( " Trying to read the effective enterprise configuration server URL. " ) ;
resolve_effective_enterprise_config_source ( )
. configs
. into_iter ( )
. next ( )
. map ( | config | config . server_url )
. unwrap_or_default ( )
2025-06-01 19:14:21 +00:00
}
2026-05-12 18:31:08 +00:00
pub async fn read_enterprise_env_config_encryption_secret ( _token : APIToken ) -> String {
2026-03-31 11:02:59 +00:00
debug! ( " Trying to read the effective enterprise configuration encryption secret. " ) ;
resolve_effective_enterprise_secret_source ( ) . encryption_secret
2026-02-15 17:11:57 +00:00
}
2026-03-31 11:02:59 +00:00
/// Returns all enterprise configurations from the effective source.
2026-05-12 18:31:08 +00:00
pub async fn read_enterprise_configs ( _token : APIToken ) -> Json < Vec < EnterpriseConfig > > {
2026-03-31 11:02:59 +00:00
info! ( " Trying to read the effective enterprise configurations. " ) ;
Json ( resolve_effective_enterprise_config_source ( ) . configs )
}
2026-06-11 09:37:40 +00:00
pub fn resolve_external_http_custom_root_certificate_policy ( ) -> ExternalHttpCustomRootCertificatePolicy {
load_external_http_custom_root_certificate_policy_from_directories ( & enterprise_policy_directories ( ) )
}
2026-03-31 11:02:59 +00:00
fn resolve_effective_enterprise_config_source ( ) -> EnterpriseSourceData {
select_effective_enterprise_config_source ( gather_enterprise_sources ( ) )
}
fn resolve_effective_enterprise_secret_source ( ) -> EnterpriseSourceData {
select_effective_enterprise_secret_source ( gather_enterprise_sources ( ) )
}
fn select_effective_enterprise_config_source (
sources : Vec < EnterpriseSourceData > ,
) -> EnterpriseSourceData {
for source in sources {
if ! source . configs . is_empty ( ) {
info! ( " Using enterprise configuration source '{}'. " , source . source_name ) ;
return source ;
}
info! ( " Enterprise configuration source '{}' did not provide any valid configurations. " , source . source_name ) ;
}
info! ( " No enterprise configuration source provided any valid configurations. " ) ;
EnterpriseSourceData ::default ( )
}
fn select_effective_enterprise_secret_source (
sources : Vec < EnterpriseSourceData > ,
) -> EnterpriseSourceData {
for source in sources {
if ! source . encryption_secret . is_empty ( ) {
info! ( " Using enterprise encryption-secret source '{}'. " , source . source_name ) ;
return source ;
}
info! ( " Enterprise encryption-secret source '{}' did not provide a usable secret. " , source . source_name ) ;
}
2026-02-15 17:11:57 +00:00
2026-03-31 11:02:59 +00:00
info! ( " No enterprise source provided an enterprise encryption secret. " ) ;
EnterpriseSourceData ::default ( )
}
2026-02-15 17:11:57 +00:00
2026-03-31 11:02:59 +00:00
fn gather_enterprise_sources ( ) -> Vec < EnterpriseSourceData > {
cfg_if ::cfg_if! {
if #[ cfg(target_os = " windows " ) ] {
vec! [
load_registry_enterprise_source ( ) ,
load_policy_file_enterprise_source ( ) ,
load_environment_enterprise_source ( ) ,
]
} else if #[ cfg(any(target_os = " linux " , target_os = " macos " )) ] {
vec! [
load_policy_file_enterprise_source ( ) ,
load_environment_enterprise_source ( ) ,
]
} else {
vec! [ load_environment_enterprise_source ( ) ]
}
}
}
#[ cfg(target_os = " windows " ) ]
fn load_registry_enterprise_source ( ) -> EnterpriseSourceData {
use windows_registry ::* ;
info! ( r "Trying to read enterprise configuration metadata from 'HKEY_CURRENT_USER\{}'." , ENTERPRISE_REGISTRY_KEY_PATH ) ;
2026-05-31 10:11:09 +00:00
let mut values = EnterpriseSourceValues ::new ( ) ;
2026-03-31 11:02:59 +00:00
let key = match CURRENT_USER . open ( ENTERPRISE_REGISTRY_KEY_PATH ) {
Ok ( key ) = > key ,
Err ( _ ) = > {
info! ( r "Could not read 'HKEY_CURRENT_USER\{}'." , ENTERPRISE_REGISTRY_KEY_PATH ) ;
return EnterpriseSourceData {
source_name : String ::from ( " Windows registry " ) ,
.. EnterpriseSourceData ::default ( )
} ;
}
} ;
2026-05-31 10:11:09 +00:00
match key . values ( ) {
Ok ( registry_values ) = > {
for ( key_name , value ) in registry_values {
let Some ( source_key_name ) = enterprise_registry_value_key_name ( & key_name ) else {
continue ;
} ;
match String ::try_from ( value ) {
Ok ( value ) = > {
values . insert ( source_key_name , EnterpriseSourceValue ::new ( value , String ::new ( ) ) ) ;
} ,
Err ( error ) = > {
warn! ( r "Could not read enterprise registry value 'HKEY_CURRENT_USER\{}\{}' as string: {}." , ENTERPRISE_REGISTRY_KEY_PATH , key_name , error ) ;
} ,
}
}
} ,
2026-03-31 11:02:59 +00:00
2026-05-31 10:11:09 +00:00
Err ( error ) = > {
warn! ( r "Could not enumerate enterprise registry values from 'HKEY_CURRENT_USER\{}': {}." , ENTERPRISE_REGISTRY_KEY_PATH , error ) ;
} ,
2026-03-31 11:02:59 +00:00
}
parse_enterprise_source_values ( " Windows registry " , & values )
}
#[ cfg(target_os = " windows " ) ]
2026-05-31 10:11:09 +00:00
fn enterprise_registry_value_key_name ( key_name : & str ) -> Option < String > {
if is_legacy_enterprise_source_key ( key_name ) {
return Some ( String ::from ( key_name ) ) ;
2026-03-31 11:02:59 +00:00
}
2026-05-31 10:11:09 +00:00
enterprise_indexed_source_key_name ( key_name )
2026-03-31 11:02:59 +00:00
}
fn load_policy_file_enterprise_source ( ) -> EnterpriseSourceData {
let directories = enterprise_policy_directories ( ) ;
info! ( " Trying to read enterprise configuration metadata from policy files in {} director{}. " , directories . len ( ) , if directories . len ( ) = = 1 { " y " } else { " ies " } ) ;
let values = load_policy_values_from_directories ( & directories ) ;
parse_enterprise_source_values ( " policy files " , & values )
}
fn load_environment_enterprise_source ( ) -> EnterpriseSourceData {
info! ( " Trying to read enterprise configuration metadata from environment variables. " ) ;
2026-05-31 10:11:09 +00:00
let mut values = EnterpriseSourceValues ::new ( ) ;
for ( env_name , value ) in env ::vars ( ) {
if let Some ( source_key_name ) = enterprise_environment_key_name ( & env_name ) {
let source_detail = enterprise_environment_source_detail ( & source_key_name , & env_name ) ;
values . insert ( source_key_name , EnterpriseSourceValue ::new ( value , source_detail ) ) ;
}
2026-03-31 11:02:59 +00:00
}
parse_enterprise_source_values ( " environment variables " , & values )
}
2026-02-15 17:11:57 +00:00
2026-05-31 10:11:09 +00:00
fn enterprise_environment_source_detail ( source_key_name : & str , env_name : & str ) -> String {
if source_key_name = = " config_id "
| | enterprise_source_key_suffix ( source_key_name , ENTERPRISE_CONFIG_ID_KEY_PREFIX ) . is_some ( ) {
String ::from ( env_name )
} else {
String ::new ( )
}
}
fn enterprise_environment_key_name ( env_name : & str ) -> Option < String > {
if enterprise_env_key_equals ( env_name , ENTERPRISE_ENV_CONFIGS ) {
return Some ( String ::from ( " configs " ) ) ;
}
if enterprise_env_key_equals ( env_name , ENTERPRISE_ENV_CONFIG_ID_PREFIX ) {
return Some ( String ::from ( " config_id " ) ) ;
}
if enterprise_env_key_equals ( env_name , ENTERPRISE_ENV_CONFIG_SERVER_URL_PREFIX ) {
return Some ( String ::from ( " config_server_url " ) ) ;
}
if enterprise_env_key_equals ( env_name , ENTERPRISE_ENV_CONFIG_ENCRYPTION_SECRET ) {
return Some ( String ::from ( " config_encryption_secret " ) ) ;
}
if let Some ( suffix ) = enterprise_env_key_suffix ( env_name , ENTERPRISE_ENV_CONFIG_ID_PREFIX ) {
return Some ( format! ( " config_id {suffix} " ) ) ;
}
if let Some ( suffix ) = enterprise_env_key_suffix ( env_name , ENTERPRISE_ENV_CONFIG_SERVER_URL_PREFIX ) {
return Some ( format! ( " config_server_url {suffix} " ) ) ;
}
None
}
#[ cfg(target_os = " windows " ) ]
fn enterprise_env_key_equals ( env_name : & str , expected : & str ) -> bool {
env_name . eq_ignore_ascii_case ( expected )
}
#[ cfg(not(target_os = " windows " )) ]
fn enterprise_env_key_equals ( env_name : & str , expected : & str ) -> bool {
env_name = = expected
}
#[ cfg(target_os = " windows " ) ]
fn enterprise_env_key_suffix < ' a > ( env_name : & ' a str , prefix : & str ) -> Option < & ' a str > {
if env_name . len ( ) < prefix . len ( ) {
return None ;
}
let ( raw_prefix , suffix ) = env_name . split_at ( prefix . len ( ) ) ;
if raw_prefix . eq_ignore_ascii_case ( prefix ) {
normalize_enterprise_slot_suffix ( suffix )
} else {
None
2026-03-31 11:02:59 +00:00
}
}
2026-05-31 10:11:09 +00:00
#[ cfg(not(target_os = " windows " )) ]
fn enterprise_env_key_suffix < ' a > ( env_name : & ' a str , prefix : & str ) -> Option < & ' a str > {
env_name
. strip_prefix ( prefix )
. and_then ( normalize_enterprise_slot_suffix )
}
2026-03-31 11:02:59 +00:00
#[ cfg(target_os = " windows " ) ]
fn enterprise_policy_directories ( ) -> Vec < PathBuf > {
let base = env ::var_os ( " ProgramData " )
. map ( PathBuf ::from )
. unwrap_or_else ( | | PathBuf ::from ( r "C:\ProgramData" ) ) ;
vec! [ base . join ( " MindWorkAI " ) . join ( " AI-Studio " ) ]
}
#[ cfg(target_os = " linux " ) ]
fn enterprise_policy_directories ( ) -> Vec < PathBuf > {
let xdg_config_dirs = env ::var ( " XDG_CONFIG_DIRS " ) . ok ( ) ;
2026-05-31 17:24:11 +00:00
linux_policy_directories_from_xdg ( xdg_config_dirs . as_deref ( ) , is_flatpak ( ) )
2026-03-31 11:02:59 +00:00
}
#[ cfg(target_os = " macos " ) ]
fn enterprise_policy_directories ( ) -> Vec < PathBuf > {
vec! [ PathBuf ::from (
" /Library/Application Support/MindWork/AI Studio " ,
) ]
}
#[ cfg(not(any(target_os = " windows " , target_os = " linux " , target_os = " macos " ))) ]
fn enterprise_policy_directories ( ) -> Vec < PathBuf > {
Vec ::new ( )
}
#[ cfg(any(target_os = " linux " , test)) ]
2026-05-31 17:24:11 +00:00
fn linux_policy_directories_from_xdg ( xdg_config_dirs : Option < & str > , include_flatpak_provisioning : bool ) -> Vec < PathBuf > {
2026-03-31 11:02:59 +00:00
let mut directories = Vec ::new ( ) ;
2026-05-31 17:24:11 +00:00
if include_flatpak_provisioning {
directories . push ( PathBuf ::from ( FLATPAK_ENTERPRISE_POLICY_DIRECTORY ) ) ;
}
let mut has_linux_policy_directory = false ;
2026-03-31 11:02:59 +00:00
if let Some ( raw_directories ) = xdg_config_dirs {
for path in raw_directories . split ( ':' ) {
if let Some ( path ) = normalize_enterprise_value ( path ) {
directories . push ( PathBuf ::from ( path ) . join ( " mindwork-ai-studio " ) ) ;
2026-05-31 17:24:11 +00:00
has_linux_policy_directory = true ;
2026-02-15 17:11:57 +00:00
}
2026-03-31 11:02:59 +00:00
}
}
2026-05-31 17:24:11 +00:00
if ! has_linux_policy_directory {
2026-03-31 11:02:59 +00:00
directories . push ( PathBuf ::from ( " /etc/xdg/mindwork-ai-studio " ) ) ;
}
2026-02-15 17:11:57 +00:00
2026-03-31 11:02:59 +00:00
directories
}
2026-05-31 10:11:09 +00:00
fn load_policy_values_from_directories ( directories : & [ PathBuf ] ) -> EnterpriseSourceValues {
let mut values = EnterpriseSourceValues ::new ( ) ;
2026-03-31 11:02:59 +00:00
for directory in directories {
info! ( " Checking enterprise policy directory '{}'. " , directory . display ( ) ) ;
2026-05-31 10:11:09 +00:00
let entries = match fs ::read_dir ( directory ) {
Ok ( entries ) = > entries ,
Err ( error ) = > {
info! ( " Could not enumerate enterprise policy directory '{}': {}. " , directory . display ( ) , error ) ;
continue ;
} ,
} ;
for entry in entries {
let entry = match entry {
Ok ( entry ) = > entry ,
Err ( error ) = > {
warn! ( " Could not read an entry from enterprise policy directory '{}': {}. " , directory . display ( ) , error ) ;
continue ;
} ,
} ;
let file_name = entry . file_name ( ) ;
let Some ( file_name ) = file_name . to_str ( ) else {
continue ;
} ;
let Some ( suffix ) = enterprise_policy_file_slot_suffix ( file_name ) else {
continue ;
} ;
let path = entry . path ( ) ;
2026-03-31 11:02:59 +00:00
if let Some ( config_values ) = read_policy_yaml_mapping ( & path ) {
2026-05-31 10:11:09 +00:00
let source_detail = path
. canonicalize ( )
. unwrap_or_else ( | _ | path . clone ( ) )
. to_string_lossy ( )
. into_owned ( ) ;
2026-03-31 11:02:59 +00:00
if let Some ( id ) = config_values . get ( " id " ) {
2026-05-31 10:11:09 +00:00
insert_first_non_empty_value ( & mut values , & format! ( " config_id {suffix} " ) , id , & source_detail ) ;
2026-03-31 11:02:59 +00:00
}
if let Some ( server_url ) = config_values . get ( " server_url " ) {
2026-05-31 10:11:09 +00:00
insert_first_non_empty_value ( & mut values , & format! ( " config_server_url {suffix} " ) , server_url , & source_detail ) ;
2026-02-15 17:11:57 +00:00
}
}
}
2026-03-31 11:02:59 +00:00
let secret_path = directory . join ( ENTERPRISE_POLICY_SECRET_FILE_NAME ) ;
2026-05-16 16:53:53 +00:00
if let Some ( secret_values ) = read_policy_yaml_mapping ( & secret_path )
& & let Some ( secret ) = secret_values . get ( " config_encryption_secret " ) {
2026-05-31 10:11:09 +00:00
insert_first_non_empty_value ( & mut values , " config_encryption_secret " , secret , " " ) ;
2026-03-31 11:02:59 +00:00
}
2026-02-15 17:11:57 +00:00
}
2026-03-31 11:02:59 +00:00
values
}
2026-02-15 17:11:57 +00:00
2026-06-11 09:37:40 +00:00
fn load_external_http_custom_root_certificate_policy_from_directories ( directories : & [ PathBuf ] ) -> ExternalHttpCustomRootCertificatePolicy {
for directory in directories {
let path = directory . join ( EXTERNAL_HTTP_CUSTOM_ROOT_CERTIFICATE_POLICY_FILE_NAME ) ;
let Some ( values ) = read_policy_yaml_mapping ( & path ) else {
continue ;
} ;
if let Some ( policy ) = parse_external_http_custom_root_certificate_policy ( & path , & values ) {
info! ( " Using external HTTP custom root certificate policy from '{}'. " , policy . source_detail ) ;
return policy ;
}
}
ExternalHttpCustomRootCertificatePolicy ::default ( )
}
fn parse_external_http_custom_root_certificate_policy ( path : & Path , values : & HashMap < String , String > ) -> Option < ExternalHttpCustomRootCertificatePolicy > {
let Some ( raw_enabled ) = values . get ( " enabled " ) else {
warn! ( " Ignoring external HTTP custom root certificate policy '{}': missing 'enabled'. " , path . display ( ) ) ;
return None ;
} ;
let Some ( enabled ) = parse_policy_boolean_value ( raw_enabled ) else {
warn! ( " Ignoring external HTTP custom root certificate policy '{}': invalid 'enabled' value. " , path . display ( ) ) ;
return None ;
} ;
let source_detail = path
. canonicalize ( )
. unwrap_or_else ( | _ | path . to_path_buf ( ) )
. to_string_lossy ( )
. into_owned ( ) ;
Some ( ExternalHttpCustomRootCertificatePolicy {
is_configured : true ,
enabled ,
bundle_path : values
. get ( " bundle_path " )
. and_then ( | value | normalize_enterprise_value ( value ) )
. unwrap_or_default ( ) ,
allowed_hosts : values
. get ( " allowed_hosts " )
. and_then ( | value | normalize_enterprise_value ( value ) )
. unwrap_or_default ( ) ,
source_detail ,
} )
}
2026-05-31 10:11:09 +00:00
fn enterprise_policy_file_slot_suffix ( file_name : & str ) -> Option < & str > {
let suffix = file_name
. strip_prefix ( " config " ) ?
. strip_suffix ( " .yaml " ) ? ;
normalize_enterprise_slot_suffix ( suffix )
}
2026-03-31 11:02:59 +00:00
fn read_policy_yaml_mapping ( path : & Path ) -> Option < HashMap < String , String > > {
if ! path . exists ( ) {
return None ;
}
2026-02-15 17:11:57 +00:00
2026-03-31 11:02:59 +00:00
let content = match fs ::read_to_string ( path ) {
Ok ( content ) = > content ,
Err ( error ) = > {
warn! ( " Could not read enterprise policy file '{}': {} " , path . display ( ) , error ) ;
return None ;
2026-02-15 17:11:57 +00:00
}
2026-03-31 11:02:59 +00:00
} ;
2026-02-15 17:11:57 +00:00
2026-03-31 11:02:59 +00:00
match parse_policy_yaml_mapping ( path , & content ) {
Some ( values ) = > Some ( values ) ,
None = > {
warn! ( " Could not parse enterprise policy file '{}'. " , path . display ( ) ) ;
None
}
}
2026-02-15 17:11:57 +00:00
}
2026-03-31 11:02:59 +00:00
fn parse_policy_yaml_mapping ( path : & Path , content : & str ) -> Option < HashMap < String , String > > {
let mut values = HashMap ::new ( ) ;
for ( line_number , line ) in content . lines ( ) . enumerate ( ) {
let trimmed = line . trim ( ) ;
if trimmed . is_empty ( ) | | trimmed . starts_with ( '#' ) {
continue ;
}
2025-06-01 19:14:21 +00:00
2026-03-31 11:02:59 +00:00
let ( key , raw_value ) = match trimmed . split_once ( ':' ) {
Some ( parts ) = > parts ,
None = > {
warn! ( " Invalid enterprise policy file '{}': line {} does not contain ':'. " , path . display ( ) , line_number + 1 ) ;
return None ;
2025-06-01 19:14:21 +00:00
}
2026-03-31 11:02:59 +00:00
} ;
let key = key . trim ( ) ;
if key . is_empty ( ) {
warn! ( " Invalid enterprise policy file '{}': line {} contains an empty key. " , path . display ( ) , line_number + 1 ) ;
return None ;
}
let value = match parse_policy_yaml_value ( raw_value ) {
Some ( value ) = > value ,
None = > {
warn! ( " Invalid enterprise policy file '{}': line {} contains an unsupported YAML value. " , path . display ( ) , line_number + 1 ) ;
return None ;
}
} ;
values . insert ( String ::from ( key ) , value ) ;
}
Some ( values )
}
fn parse_policy_yaml_value ( raw_value : & str ) -> Option < String > {
let trimmed = raw_value . trim ( ) ;
if trimmed . is_empty ( ) {
return Some ( String ::new ( ) ) ;
}
if trimmed . starts_with ( '"' ) | | trimmed . ends_with ( '"' ) {
if trimmed . len ( ) > = 2 & & trimmed . starts_with ( '"' ) & & trimmed . ends_with ( '"' ) {
return Some ( trimmed [ 1 .. trimmed . len ( ) - 1 ] . to_string ( ) ) ;
}
return None ;
}
if trimmed . starts_with ( '\'' ) | | trimmed . ends_with ( '\'' ) {
if trimmed . len ( ) > = 2 & & trimmed . starts_with ( '\'' ) & & trimmed . ends_with ( '\'' ) {
return Some ( trimmed [ 1 .. trimmed . len ( ) - 1 ] . to_string ( ) ) ;
}
return None ;
}
Some ( String ::from ( trimmed ) )
}
2026-06-11 09:37:40 +00:00
fn parse_policy_boolean_value ( raw_value : & str ) -> Option < bool > {
let normalized = raw_value . trim ( ) ;
if normalized . eq_ignore_ascii_case ( " true " )
| | normalized = = " 1 "
| | normalized . eq_ignore_ascii_case ( " yes " )
| | normalized . eq_ignore_ascii_case ( " on " ) {
return Some ( true ) ;
}
if normalized . eq_ignore_ascii_case ( " false " )
| | normalized = = " 0 "
| | normalized . eq_ignore_ascii_case ( " no " )
| | normalized . eq_ignore_ascii_case ( " off " ) {
return Some ( false ) ;
}
None
}
2026-05-31 10:11:09 +00:00
fn insert_first_non_empty_value ( values : & mut EnterpriseSourceValues , key : & str , raw_value : & str , source_detail : & str ) {
2026-03-31 11:02:59 +00:00
if let Some ( value ) = normalize_enterprise_value ( raw_value ) {
2026-05-31 10:11:09 +00:00
values
. entry ( String ::from ( key ) )
. or_insert_with ( | | EnterpriseSourceValue ::new ( value , String ::from ( source_detail ) ) ) ;
}
}
#[ cfg(target_os = " windows " ) ]
fn is_legacy_enterprise_source_key ( key_name : & str ) -> bool {
matches! (
key_name ,
" configs " | " config_id " | " config_server_url " | " config_encryption_secret "
)
}
#[ cfg(target_os = " windows " ) ]
fn enterprise_indexed_source_key_name ( key_name : & str ) -> Option < String > {
if let Some ( suffix ) = enterprise_source_key_suffix ( key_name , ENTERPRISE_CONFIG_ID_KEY_PREFIX ) {
return Some ( format! ( " config_id {suffix} " ) ) ;
}
if let Some ( suffix ) = enterprise_source_key_suffix ( key_name , ENTERPRISE_CONFIG_SERVER_URL_KEY_PREFIX ) {
return Some ( format! ( " config_server_url {suffix} " ) ) ;
2026-03-31 11:02:59 +00:00
}
2026-05-31 10:11:09 +00:00
None
}
fn enterprise_source_key_suffix < ' a > ( key_name : & ' a str , prefix : & str ) -> Option < & ' a str > {
key_name
. strip_prefix ( prefix )
. and_then ( normalize_enterprise_slot_suffix )
2026-03-31 11:02:59 +00:00
}
2026-05-31 10:11:09 +00:00
fn normalize_enterprise_slot_suffix ( raw_suffix : & str ) -> Option < & str > {
let suffix = raw_suffix . strip_prefix ( '_' ) . unwrap_or ( raw_suffix ) ;
if is_enterprise_slot_suffix ( suffix ) {
Some ( suffix )
} else {
None
}
}
fn is_enterprise_slot_suffix ( suffix : & str ) -> bool {
! suffix . is_empty ( )
& & suffix . len ( ) < = ENTERPRISE_CONFIG_SLOT_WIDTH
& & suffix . chars ( ) . all ( | c | c . is_ascii_digit ( ) )
& & suffix . parse ::< u32 > ( ) . is_ok_and ( | index | index < = ENTERPRISE_CONFIG_SLOT_MAX )
}
fn collect_enterprise_config_slots < T : EnterpriseSourceValueAccess > ( values : & HashMap < String , T > ) -> Vec < String > {
let mut slots = HashSet ::new ( ) ;
for key_name in values . keys ( ) {
if let Some ( suffix ) = enterprise_source_key_suffix ( key_name , ENTERPRISE_CONFIG_ID_KEY_PREFIX )
& & is_enterprise_slot_suffix ( suffix ) {
slots . insert ( String ::from ( suffix ) ) ;
continue ;
}
if let Some ( suffix ) = enterprise_source_key_suffix ( key_name , ENTERPRISE_CONFIG_SERVER_URL_KEY_PREFIX )
& & is_enterprise_slot_suffix ( suffix ) {
slots . insert ( String ::from ( suffix ) ) ;
}
}
let mut slots : Vec < String > = slots . into_iter ( ) . collect ( ) ;
slots . sort_by ( | left , right | {
let left_index = left . parse ::< u32 > ( ) . unwrap_or ( ENTERPRISE_CONFIG_SLOT_MAX ) ;
let right_index = right . parse ::< u32 > ( ) . unwrap_or ( ENTERPRISE_CONFIG_SLOT_MAX ) ;
left_index
. cmp ( & right_index )
. then_with ( | | enterprise_slot_width_rank ( left ) . cmp ( & enterprise_slot_width_rank ( right ) ) )
. then_with ( | | left . len ( ) . cmp ( & right . len ( ) ) )
. then_with ( | | left . cmp ( right ) )
} ) ;
slots
}
fn enterprise_slot_width_rank ( suffix : & str ) -> u8 {
if suffix . len ( ) = = ENTERPRISE_CONFIG_SLOT_WIDTH {
0
} else {
1
}
}
fn indexed_enterprise_source_value < ' a , T : EnterpriseSourceValueAccess > (
values : & ' a HashMap < String , T > ,
prefix : & str ,
suffix : & str ,
) -> Option < & ' a T > {
let separated_key = format! ( " {prefix} _ {suffix} " ) ;
values
. get ( & separated_key )
. or_else ( | | values . get ( & format! ( " {prefix} {suffix} " ) ) )
}
fn parse_enterprise_source_values < T : EnterpriseSourceValueAccess > (
2026-03-31 11:02:59 +00:00
source_name : & str ,
2026-05-31 10:11:09 +00:00
values : & HashMap < String , T > ,
2026-03-31 11:02:59 +00:00
) -> EnterpriseSourceData {
let mut configs = Vec ::new ( ) ;
let mut seen_ids = HashSet ::new ( ) ;
2026-05-31 10:11:09 +00:00
for suffix in collect_enterprise_config_slots ( values ) {
2026-03-31 11:02:59 +00:00
add_enterprise_config_pair (
source_name ,
2026-05-31 10:11:09 +00:00
& format! ( " indexed slot {suffix} " ) ,
indexed_enterprise_source_value ( values , ENTERPRISE_CONFIG_ID_KEY_PREFIX , & suffix ) ,
indexed_enterprise_source_value ( values , ENTERPRISE_CONFIG_SERVER_URL_KEY_PREFIX , & suffix ) ,
2026-03-31 11:02:59 +00:00
& mut configs ,
& mut seen_ids ,
) ;
}
if let Some ( combined ) = values
. get ( " configs " )
2026-05-31 10:11:09 +00:00
. and_then ( | value | normalize_enterprise_value ( value . value ( ) ) )
2026-03-31 11:02:59 +00:00
{
add_combined_enterprise_configs ( source_name , & combined , & mut configs , & mut seen_ids ) ;
}
add_enterprise_config_pair (
source_name ,
" legacy single configuration " ,
2026-05-31 10:11:09 +00:00
values . get ( " config_id " ) ,
values . get ( " config_server_url " ) ,
2026-03-31 11:02:59 +00:00
& mut configs ,
& mut seen_ids ,
) ;
let encryption_secret = values
. get ( " config_encryption_secret " )
2026-05-31 10:11:09 +00:00
. and_then ( | value | normalize_enterprise_value ( value . value ( ) ) )
2026-03-31 11:02:59 +00:00
. unwrap_or_default ( ) ;
EnterpriseSourceData {
source_name : String ::from ( source_name ) ,
configs ,
encryption_secret ,
}
}
fn add_enterprise_config_pair (
source_name : & str ,
2026-05-31 10:11:09 +00:00
slot : & str ,
raw_id : Option < & impl EnterpriseSourceValueAccess > ,
raw_server_url : Option < & impl EnterpriseSourceValueAccess > ,
2026-03-31 11:02:59 +00:00
configs : & mut Vec < EnterpriseConfig > ,
seen_ids : & mut HashSet < String > ,
) {
2026-05-31 10:11:09 +00:00
let id = raw_id . and_then ( | value | normalize_enterprise_config_id ( value . value ( ) ) ) ;
let server_url = raw_server_url . and_then ( | value | normalize_enterprise_value ( value . value ( ) ) ) ;
2026-03-31 11:02:59 +00:00
match ( id , server_url ) {
( Some ( id ) , Some ( server_url ) ) = > {
if seen_ids . insert ( id . clone ( ) ) {
2026-05-31 10:11:09 +00:00
configs . push ( EnterpriseConfig {
id ,
server_url ,
source : String ::from ( source_name ) ,
source_detail : raw_id . map ( | value | String ::from ( value . source_detail ( ) ) ) . unwrap_or_default ( ) ,
slot : String ::from ( slot ) ,
} ) ;
2026-03-31 11:02:59 +00:00
} else {
2026-05-31 10:11:09 +00:00
info! ( " Ignoring duplicate enterprise configuration '{}' from {} in '{}'. " , id , source_name , slot ) ;
2025-06-01 19:14:21 +00:00
}
}
2026-03-31 11:02:59 +00:00
( Some ( _ ) , None ) | ( None , Some ( _ ) ) = > {
2026-05-31 10:11:09 +00:00
warn! ( " Ignoring incomplete enterprise configuration from {} in '{}'. " , source_name , slot ) ;
2026-03-31 11:02:59 +00:00
}
( None , None ) = > { }
}
}
fn add_combined_enterprise_configs (
source_name : & str ,
combined : & str ,
configs : & mut Vec < EnterpriseConfig > ,
seen_ids : & mut HashSet < String > ,
) {
for ( index , entry ) in combined . split ( ';' ) . enumerate ( ) {
let trimmed = entry . trim ( ) ;
if trimmed . is_empty ( ) {
continue ;
}
let Some ( ( raw_id , raw_server_url ) ) = trimmed . split_once ( '@' ) else {
warn! ( " Ignoring malformed enterprise configuration entry '{}' from {} in combined legacy format. " , trimmed , source_name ) ;
continue ;
} ;
2026-05-31 10:11:09 +00:00
let id = EnterpriseSourceValue ::new ( String ::from ( raw_id ) , String ::new ( ) ) ;
let server_url = EnterpriseSourceValue ::new ( String ::from ( raw_server_url ) , String ::new ( ) ) ;
2026-03-31 11:02:59 +00:00
add_enterprise_config_pair (
source_name ,
& format! ( " combined legacy entry {} " , index + 1 ) ,
2026-05-31 10:11:09 +00:00
Some ( & id ) ,
Some ( & server_url ) ,
2026-03-31 11:02:59 +00:00
configs ,
seen_ids ,
) ;
}
}
fn normalize_enterprise_value ( value : & str ) -> Option < String > {
let trimmed = value . trim ( ) ;
if trimmed . is_empty ( ) {
None
} else {
Some ( String ::from ( trimmed ) )
2025-06-01 19:14:21 +00:00
}
2026-02-16 11:13:36 +00:00
}
2026-03-31 11:02:59 +00:00
fn normalize_enterprise_config_id ( value : & str ) -> Option < String > {
normalize_enterprise_value ( value ) . map ( | value | value . to_lowercase ( ) )
}
#[ cfg(test) ]
mod tests {
use super ::{
2026-05-31 10:11:09 +00:00
enterprise_environment_key_name , enterprise_policy_file_slot_suffix ,
2026-06-11 09:37:40 +00:00
load_external_http_custom_root_certificate_policy_from_directories ,
2026-03-31 11:02:59 +00:00
linux_policy_directories_from_xdg , load_policy_values_from_directories ,
normalize_locale_tag , parse_enterprise_source_values ,
select_effective_enterprise_config_source , select_effective_enterprise_secret_source ,
2026-05-31 10:11:09 +00:00
EnterpriseConfig , EnterpriseSourceData , EnterpriseSourceValue , EnterpriseSourceValues ,
2026-06-11 09:37:40 +00:00
ExternalHttpCustomRootCertificatePolicy ,
2026-03-31 11:02:59 +00:00
} ;
use std ::collections ::HashMap ;
use std ::fs ;
use std ::path ::PathBuf ;
use tempfile ::tempdir ;
const TEST_ID_A : & str = " 9072B77D-CA81-40DA-BE6A-861DA525EF7B " ;
const TEST_ID_B : & str = " a1b2c3d4-e5f6-7890-abcd-ef1234567890 " ;
const TEST_ID_C : & str = " 11111111-2222-3333-4444-555555555555 " ;
2026-05-31 10:11:09 +00:00
fn enterprise_config (
id : & str ,
server_url : & str ,
source : & str ,
source_detail : & str ,
slot : & str ,
) -> EnterpriseConfig {
EnterpriseConfig {
id : String ::from ( id ) ,
server_url : String ::from ( server_url ) ,
source : String ::from ( source ) ,
source_detail : String ::from ( source_detail ) ,
slot : String ::from ( slot ) ,
}
}
fn policy_path ( path : PathBuf ) -> String {
path
. canonicalize ( )
. unwrap_or ( path )
. to_string_lossy ( )
. into_owned ( )
}
2026-03-31 11:02:59 +00:00
#[ test ]
fn normalize_locale_tag_supports_common_linux_formats ( ) {
assert_eq! (
normalize_locale_tag ( " de_DE.UTF-8 " ) ,
Some ( String ::from ( " de-DE " ) )
) ;
assert_eq! (
normalize_locale_tag ( " de_DE@euro " ) ,
Some ( String ::from ( " de-DE " ) )
) ;
assert_eq! ( normalize_locale_tag ( " de " ) , Some ( String ::from ( " de " ) ) ) ;
assert_eq! ( normalize_locale_tag ( " en-US " ) , Some ( String ::from ( " en-US " ) ) ) ;
}
#[ test ]
fn normalize_locale_tag_rejects_non_language_locales ( ) {
assert_eq! ( normalize_locale_tag ( " C " ) , None ) ;
assert_eq! ( normalize_locale_tag ( " C.UTF-8 " ) , None ) ;
assert_eq! ( normalize_locale_tag ( " POSIX " ) , None ) ;
assert_eq! ( normalize_locale_tag ( " " ) , None ) ;
}
#[ test ]
fn parse_enterprise_source_values_prefers_indexed_then_combined_then_legacy ( ) {
let mut values = HashMap ::new ( ) ;
values . insert ( String ::from ( " config_id0 " ) , String ::from ( TEST_ID_A ) ) ;
values . insert (
String ::from ( " config_server_url0 " ) ,
String ::from ( " https://indexed.example.org " ) ,
) ;
values . insert (
String ::from ( " configs " ) ,
format! (
" {TEST_ID_A}@https://duplicate.example.org;{TEST_ID_B}@https://combined.example.org "
) ,
) ;
values . insert ( String ::from ( " config_id " ) , String ::from ( TEST_ID_C ) ) ;
values . insert (
String ::from ( " config_server_url " ) ,
String ::from ( " https://legacy.example.org " ) ,
) ;
values . insert (
String ::from ( " config_encryption_secret " ) ,
String ::from ( " secret " ) ,
) ;
let source = parse_enterprise_source_values ( " test " , & values ) ;
assert_eq! (
source . configs ,
vec! [
2026-05-31 10:11:09 +00:00
enterprise_config ( " 9072b77d-ca81-40da-be6a-861da525ef7b " , " https://indexed.example.org " , " test " , " " , " indexed slot 0 " ) ,
enterprise_config ( TEST_ID_B , " https://combined.example.org " , " test " , " " , " combined legacy entry 2 " ) ,
enterprise_config ( TEST_ID_C , " https://legacy.example.org " , " test " , " " , " legacy single configuration " ) ,
2026-03-31 11:02:59 +00:00
]
) ;
assert_eq! ( source . encryption_secret , " secret " ) ;
}
#[ test ]
fn parse_enterprise_source_values_supports_gaps_between_indexed_slots ( ) {
let mut values = HashMap ::new ( ) ;
values . insert ( String ::from ( " config_id0 " ) , String ::from ( TEST_ID_A ) ) ;
values . insert (
String ::from ( " config_server_url0 " ) ,
String ::from ( " https://slot0.example.org " ) ,
) ;
values . insert ( String ::from ( " config_id4 " ) , String ::from ( TEST_ID_B ) ) ;
values . insert (
String ::from ( " config_server_url4 " ) ,
String ::from ( " https://slot4.example.org " ) ,
) ;
let source = parse_enterprise_source_values ( " test " , & values ) ;
assert_eq! (
source . configs ,
vec! [
2026-05-31 10:11:09 +00:00
enterprise_config ( " 9072b77d-ca81-40da-be6a-861da525ef7b " , " https://slot0.example.org " , " test " , " " , " indexed slot 0 " ) ,
enterprise_config ( TEST_ID_B , " https://slot4.example.org " , " test " , " " , " indexed slot 4 " ) ,
]
) ;
}
#[ test ]
fn parse_enterprise_source_values_supports_padded_and_high_indexed_slots ( ) {
let mut values = HashMap ::new ( ) ;
values . insert ( String ::from ( " config_id_00000 " ) , String ::from ( TEST_ID_A ) ) ;
values . insert (
String ::from ( " config_server_url_00000 " ) ,
String ::from ( " https://slot0.example.org " ) ,
) ;
values . insert ( String ::from ( " config_id_10503 " ) , String ::from ( TEST_ID_B ) ) ;
values . insert (
String ::from ( " config_server_url_10503 " ) ,
String ::from ( " https://slot10503.example.org " ) ,
) ;
let source = parse_enterprise_source_values ( " test " , & values ) ;
assert_eq! (
source . configs ,
vec! [
enterprise_config ( " 9072b77d-ca81-40da-be6a-861da525ef7b " , " https://slot0.example.org " , " test " , " " , " indexed slot 00000 " ) ,
enterprise_config ( TEST_ID_B , " https://slot10503.example.org " , " test " , " " , " indexed slot 10503 " ) ,
]
) ;
}
#[ test ]
fn parse_enterprise_source_values_treats_slot_widths_as_distinct_slots ( ) {
let mut values = HashMap ::new ( ) ;
values . insert ( String ::from ( " config_id_00001 " ) , String ::from ( TEST_ID_A ) ) ;
values . insert (
String ::from ( " config_server_url_00001 " ) ,
String ::from ( " https://padded.example.org " ) ,
) ;
values . insert ( String ::from ( " config_id1 " ) , String ::from ( TEST_ID_B ) ) ;
values . insert (
String ::from ( " config_server_url1 " ) ,
String ::from ( " https://legacy-slot.example.org " ) ,
) ;
let source = parse_enterprise_source_values ( " test " , & values ) ;
assert_eq! (
source . configs ,
vec! [
enterprise_config ( " 9072b77d-ca81-40da-be6a-861da525ef7b " , " https://padded.example.org " , " test " , " " , " indexed slot 00001 " ) ,
enterprise_config ( TEST_ID_B , " https://legacy-slot.example.org " , " test " , " " , " indexed slot 1 " ) ,
2026-03-31 11:02:59 +00:00
]
) ;
}
2026-05-31 10:11:09 +00:00
#[ test ]
fn parse_enterprise_source_values_ignores_invalid_slot_suffixes ( ) {
let mut values = HashMap ::new ( ) ;
values . insert ( String ::from ( " config_id_99999 " ) , String ::from ( TEST_ID_A ) ) ;
values . insert (
String ::from ( " config_server_url_99999 " ) ,
String ::from ( " https://valid.example.org " ) ,
) ;
values . insert ( String ::from ( " config_id_100000 " ) , String ::from ( TEST_ID_B ) ) ;
values . insert (
String ::from ( " config_server_url_100000 " ) ,
String ::from ( " https://too-high.example.org " ) ,
) ;
values . insert ( String ::from ( " config_id_abc " ) , String ::from ( TEST_ID_C ) ) ;
values . insert (
String ::from ( " config_server_url_abc " ) ,
String ::from ( " https://letters.example.org " ) ,
) ;
let source = parse_enterprise_source_values ( " test " , & values ) ;
assert_eq! (
source . configs ,
vec! [ enterprise_config ( " 9072b77d-ca81-40da-be6a-861da525ef7b " , " https://valid.example.org " , " test " , " " , " indexed slot 99999 " ) ]
) ;
}
#[ test ]
fn enterprise_environment_key_name_maps_indexed_and_legacy_names ( ) {
assert_eq! (
enterprise_environment_key_name ( " MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID_10503 " ) ,
Some ( String ::from ( " config_id10503 " ) )
) ;
assert_eq! (
enterprise_environment_key_name ( " MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_SERVER_URL_00000 " ) ,
Some ( String ::from ( " config_server_url00000 " ) )
) ;
assert_eq! (
enterprise_environment_key_name ( " MINDWORK_AI_STUDIO_ENTERPRISE_CONFIGS " ) ,
Some ( String ::from ( " configs " ) )
) ;
assert_eq! (
enterprise_environment_key_name ( " MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID_100000 " ) ,
None
) ;
}
#[ test ]
fn parse_enterprise_source_values_keeps_environment_id_variable_as_source_detail ( ) {
let mut values = EnterpriseSourceValues ::new ( ) ;
values . insert (
String ::from ( " config_id00000 " ) ,
EnterpriseSourceValue ::new (
String ::from ( TEST_ID_A ) ,
String ::from ( " MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID_00000 " ) ,
) ,
) ;
values . insert (
String ::from ( " config_server_url00000 " ) ,
EnterpriseSourceValue ::new ( String ::from ( " https://env.example.org " ) , String ::new ( ) ) ,
) ;
let source = parse_enterprise_source_values ( " environment variables " , & values ) ;
assert_eq! (
source . configs ,
vec! [ enterprise_config (
" 9072b77d-ca81-40da-be6a-861da525ef7b " ,
" https://env.example.org " ,
" environment variables " ,
" MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID_00000 " ,
" indexed slot 00000 "
) ]
) ;
}
#[ test ]
fn enterprise_policy_file_slot_suffix_accepts_valid_slot_file_names ( ) {
assert_eq! ( enterprise_policy_file_slot_suffix ( " config0.yaml " ) , Some ( " 0 " ) ) ;
assert_eq! (
enterprise_policy_file_slot_suffix ( " config_00000.yaml " ) ,
Some ( " 00000 " )
) ;
assert_eq! (
enterprise_policy_file_slot_suffix ( " config_10503.yaml " ) ,
Some ( " 10503 " )
) ;
assert_eq! ( enterprise_policy_file_slot_suffix ( " config_100000.yaml " ) , None ) ;
assert_eq! ( enterprise_policy_file_slot_suffix ( " config_abc.yaml " ) , None ) ;
}
2026-03-31 11:02:59 +00:00
#[ test ]
fn select_effective_enterprise_config_source_uses_first_source_with_configs_only ( ) {
let selected = select_effective_enterprise_config_source ( vec! [
EnterpriseSourceData {
source_name : String ::from ( " registry " ) ,
2026-05-31 10:11:09 +00:00
configs : vec ! [ enterprise_config ( & TEST_ID_A . to_lowercase ( ) , " https://registry.example.org " , " registry " , " " , " indexed slot 0 " ) ] ,
2026-03-31 11:02:59 +00:00
encryption_secret : String ::new ( ) ,
} ,
EnterpriseSourceData {
source_name : String ::from ( " environment " ) ,
2026-05-31 10:11:09 +00:00
configs : vec ! [ enterprise_config ( TEST_ID_B , " https://env.example.org " , " environment " , " " , " indexed slot 0 " ) ] ,
2026-03-31 11:02:59 +00:00
encryption_secret : String ::from ( " ENV-SECRET " ) ,
} ,
] ) ;
assert_eq! ( selected . source_name , " registry " ) ;
assert_eq! ( selected . encryption_secret , " " ) ;
assert_eq! ( selected . configs . len ( ) , 1 ) ;
}
#[ test ]
fn select_effective_enterprise_secret_source_allows_secret_only_source ( ) {
let selected = select_effective_enterprise_secret_source ( vec! [
EnterpriseSourceData {
source_name : String ::from ( " policy files " ) ,
configs : Vec ::new ( ) ,
encryption_secret : String ::from ( " POLICY-SECRET " ) ,
} ,
EnterpriseSourceData {
source_name : String ::from ( " environment " ) ,
2026-05-31 10:11:09 +00:00
configs : vec ! [ enterprise_config ( TEST_ID_B , " https://env.example.org " , " environment " , " " , " indexed slot 0 " ) ] ,
2026-03-31 11:02:59 +00:00
encryption_secret : String ::new ( ) ,
} ,
] ) ;
assert_eq! ( selected . source_name , " policy files " ) ;
assert_eq! ( selected . encryption_secret , " POLICY-SECRET " ) ;
assert! ( selected . configs . is_empty ( ) ) ;
}
#[ test ]
fn select_effective_enterprise_secret_source_falls_back_independently_from_configs ( ) {
let selected = select_effective_enterprise_secret_source ( vec! [
EnterpriseSourceData {
source_name : String ::from ( " registry " ) ,
2026-05-31 10:11:09 +00:00
configs : vec ! [ enterprise_config ( & TEST_ID_A . to_lowercase ( ) , " https://registry.example.org " , " registry " , " " , " indexed slot 0 " ) ] ,
2026-03-31 11:02:59 +00:00
encryption_secret : String ::new ( ) ,
} ,
EnterpriseSourceData {
source_name : String ::from ( " environment " ) ,
configs : Vec ::new ( ) ,
encryption_secret : String ::from ( " ENV-SECRET " ) ,
} ,
] ) ;
assert_eq! ( selected . source_name , " environment " ) ;
assert_eq! ( selected . encryption_secret , " ENV-SECRET " ) ;
assert! ( selected . configs . is_empty ( ) ) ;
}
#[ test ]
fn select_effective_enterprise_secret_source_ignores_empty_secrets ( ) {
let selected = select_effective_enterprise_secret_source ( vec! [
EnterpriseSourceData {
source_name : String ::from ( " policy files " ) ,
configs : Vec ::new ( ) ,
encryption_secret : String ::new ( ) ,
} ,
EnterpriseSourceData {
source_name : String ::from ( " environment " ) ,
configs : Vec ::new ( ) ,
encryption_secret : String ::from ( " VALID-SECRET " ) ,
} ,
] ) ;
assert_eq! ( selected . source_name , " environment " ) ;
assert_eq! ( selected . encryption_secret , " VALID-SECRET " ) ;
}
#[ test ]
fn parse_enterprise_source_values_supports_secret_without_configs ( ) {
let mut values = HashMap ::new ( ) ;
values . insert (
String ::from ( " config_encryption_secret " ) ,
String ::from ( " SECRET-ONLY " ) ,
) ;
let source = parse_enterprise_source_values ( " environment variables " , & values ) ;
assert! ( source . configs . is_empty ( ) ) ;
assert_eq! ( source . encryption_secret , " SECRET-ONLY " ) ;
}
#[ test ]
fn linux_policy_directories_from_xdg_preserves_order_and_falls_back ( ) {
assert_eq! (
2026-05-31 17:24:11 +00:00
linux_policy_directories_from_xdg ( Some ( " /opt/company:/etc/xdg " ) , false ) ,
2026-03-31 11:02:59 +00:00
vec! [
PathBuf ::from ( " /opt/company/mindwork-ai-studio " ) ,
PathBuf ::from ( " /etc/xdg/mindwork-ai-studio " ) ,
]
) ;
assert_eq! (
2026-05-31 17:24:11 +00:00
linux_policy_directories_from_xdg ( Some ( " : " ) , false ) ,
2026-03-31 11:02:59 +00:00
vec! [ PathBuf ::from ( " /etc/xdg/mindwork-ai-studio " ) ]
) ;
assert_eq! (
2026-05-31 17:24:11 +00:00
linux_policy_directories_from_xdg ( None , false ) ,
2026-03-31 11:02:59 +00:00
vec! [ PathBuf ::from ( " /etc/xdg/mindwork-ai-studio " ) ]
) ;
}
2026-05-31 17:24:11 +00:00
#[ test ]
fn linux_policy_directories_from_xdg_checks_flatpak_provisioning_first ( ) {
assert_eq! (
linux_policy_directories_from_xdg ( Some ( " /opt/company:/etc/xdg " ) , true ) ,
vec! [
PathBuf ::from ( " /app/etc/MindWorkAI " ) ,
PathBuf ::from ( " /opt/company/mindwork-ai-studio " ) ,
PathBuf ::from ( " /etc/xdg/mindwork-ai-studio " ) ,
]
) ;
assert_eq! (
linux_policy_directories_from_xdg ( None , true ) ,
vec! [
PathBuf ::from ( " /app/etc/MindWorkAI " ) ,
PathBuf ::from ( " /etc/xdg/mindwork-ai-studio " ) ,
]
) ;
}
2026-03-31 11:02:59 +00:00
#[ test ]
fn load_policy_values_from_directories_uses_first_directory_wins ( ) {
let directory_a = tempdir ( ) . unwrap ( ) ;
let directory_b = tempdir ( ) . unwrap ( ) ;
fs ::write (
directory_a . path ( ) . join ( " config0.yaml " ) ,
" id: \" 9072b77d-ca81-40da-be6a-861da525ef7b \" \n server_url: \" https://org.example.org \" " ,
)
. unwrap ( ) ;
fs ::write (
directory_a . path ( ) . join ( " config_encryption_secret.yaml " ) ,
" config_encryption_secret: \" SECRET-A \" " ,
)
. unwrap ( ) ;
fs ::write (
directory_b . path ( ) . join ( " config0.yaml " ) ,
" id: \" aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa \" \n server_url: \" https://ignored.example.org \" " ,
)
. unwrap ( ) ;
fs ::write (
directory_b . path ( ) . join ( " config1.yaml " ) ,
" id: \" bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb \" \n server_url: \" https://dept.example.org \" " ,
)
. unwrap ( ) ;
fs ::write (
directory_b . path ( ) . join ( " config_encryption_secret.yaml " ) ,
" config_encryption_secret: \" SECRET-B \" " ,
)
. unwrap ( ) ;
let values = load_policy_values_from_directories ( & [
directory_a . path ( ) . to_path_buf ( ) ,
directory_b . path ( ) . to_path_buf ( ) ,
] ) ;
assert_eq! (
2026-05-31 10:11:09 +00:00
values . get ( " config_id0 " ) . map ( | value | value . value . as_str ( ) ) ,
2026-03-31 11:02:59 +00:00
Some ( " 9072b77d-ca81-40da-be6a-861da525ef7b " )
) ;
assert_eq! (
2026-05-31 10:11:09 +00:00
values . get ( " config_server_url0 " ) . map ( | value | value . value . as_str ( ) ) ,
2026-03-31 11:02:59 +00:00
Some ( " https://org.example.org " )
) ;
assert_eq! (
2026-05-31 10:11:09 +00:00
values . get ( " config_id1 " ) . map ( | value | value . value . as_str ( ) ) ,
2026-03-31 11:02:59 +00:00
Some ( " bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb " )
) ;
assert_eq! (
2026-05-31 10:11:09 +00:00
values . get ( " config_encryption_secret " ) . map ( | value | value . value . as_str ( ) ) ,
2026-03-31 11:02:59 +00:00
Some ( " SECRET-A " )
) ;
}
#[ test ]
fn load_policy_values_from_directories_supports_gaps_between_policy_slots ( ) {
let directory = tempdir ( ) . unwrap ( ) ;
fs ::write (
directory . path ( ) . join ( " config0.yaml " ) ,
" id: \" 9072b77d-ca81-40da-be6a-861da525ef7b \" \n server_url: \" https://slot0.example.org \" " ,
)
. unwrap ( ) ;
fs ::write (
directory . path ( ) . join ( " config4.yaml " ) ,
" id: \" a1b2c3d4-e5f6-7890-abcd-ef1234567890 \" \n server_url: \" https://slot4.example.org \" " ,
)
. unwrap ( ) ;
let values = load_policy_values_from_directories ( & [ directory . path ( ) . to_path_buf ( ) ] ) ;
let source = parse_enterprise_source_values ( " policy files " , & values ) ;
assert_eq! (
source . configs ,
vec! [
2026-05-31 10:11:09 +00:00
enterprise_config ( " 9072b77d-ca81-40da-be6a-861da525ef7b " , " https://slot0.example.org " , " policy files " , & policy_path ( directory . path ( ) . join ( " config0.yaml " ) ) , " indexed slot 0 " ) ,
enterprise_config ( TEST_ID_B , " https://slot4.example.org " , " policy files " , & policy_path ( directory . path ( ) . join ( " config4.yaml " ) ) , " indexed slot 4 " ) ,
]
) ;
}
#[ test ]
fn load_policy_values_from_directories_supports_padded_and_high_policy_slots ( ) {
let directory = tempdir ( ) . unwrap ( ) ;
fs ::write (
directory . path ( ) . join ( " config_00000.yaml " ) ,
" id: \" 9072b77d-ca81-40da-be6a-861da525ef7b \" \n server_url: \" https://slot0.example.org \" " ,
)
. unwrap ( ) ;
fs ::write (
directory . path ( ) . join ( " config_10503.yaml " ) ,
" id: \" a1b2c3d4-e5f6-7890-abcd-ef1234567890 \" \n server_url: \" https://slot10503.example.org \" " ,
)
. unwrap ( ) ;
fs ::write (
directory . path ( ) . join ( " config_100000.yaml " ) ,
" id: \" 11111111-2222-3333-4444-555555555555 \" \n server_url: \" https://ignored.example.org \" " ,
)
. unwrap ( ) ;
let values = load_policy_values_from_directories ( & [ directory . path ( ) . to_path_buf ( ) ] ) ;
let source = parse_enterprise_source_values ( " policy files " , & values ) ;
assert_eq! (
source . configs ,
vec! [
enterprise_config ( " 9072b77d-ca81-40da-be6a-861da525ef7b " , " https://slot0.example.org " , " policy files " , & policy_path ( directory . path ( ) . join ( " config_00000.yaml " ) ) , " indexed slot 00000 " ) ,
enterprise_config ( TEST_ID_B , " https://slot10503.example.org " , " policy files " , & policy_path ( directory . path ( ) . join ( " config_10503.yaml " ) ) , " indexed slot 10503 " ) ,
2026-03-31 11:02:59 +00:00
]
) ;
}
#[ test ]
fn load_policy_values_from_directories_supports_secret_only_policy_files ( ) {
let directory = tempdir ( ) . unwrap ( ) ;
fs ::write (
directory . path ( ) . join ( " config_encryption_secret.yaml " ) ,
" config_encryption_secret: \" POLICY-SECRET \" " ,
)
. unwrap ( ) ;
let values = load_policy_values_from_directories ( & [ directory . path ( ) . to_path_buf ( ) ] ) ;
let source = parse_enterprise_source_values ( " policy files " , & values ) ;
assert! ( source . configs . is_empty ( ) ) ;
assert_eq! ( source . encryption_secret , " POLICY-SECRET " ) ;
}
2026-06-11 09:37:40 +00:00
#[ test ]
fn load_external_http_custom_root_certificate_policy_uses_first_valid_directory ( ) {
let directory_a = tempdir ( ) . unwrap ( ) ;
let directory_b = tempdir ( ) . unwrap ( ) ;
fs ::write (
directory_a . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ,
" enabled: true \n bundle_path: \" /app/etc/MindWorkAI/company-a.pem \" \n allowed_hosts: \" *.a.example.org;eri.a.example.org \" " ,
)
. unwrap ( ) ;
fs ::write (
directory_b . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ,
" enabled: true \n bundle_path: \" /app/etc/MindWorkAI/company-b.pem \" \n allowed_hosts: \" *.b.example.org \" " ,
)
. unwrap ( ) ;
let policy = load_external_http_custom_root_certificate_policy_from_directories ( & [
directory_a . path ( ) . to_path_buf ( ) ,
directory_b . path ( ) . to_path_buf ( ) ,
] ) ;
assert_eq! (
policy ,
ExternalHttpCustomRootCertificatePolicy {
is_configured : true ,
enabled : true ,
bundle_path : String ::from ( " /app/etc/MindWorkAI/company-a.pem " ) ,
allowed_hosts : String ::from ( " *.a.example.org;eri.a.example.org " ) ,
source_detail : policy_path ( directory_a . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ) ,
}
) ;
}
#[ test ]
fn load_external_http_custom_root_certificate_policy_allows_disabled_policy_to_win ( ) {
let directory_a = tempdir ( ) . unwrap ( ) ;
let directory_b = tempdir ( ) . unwrap ( ) ;
fs ::write (
directory_a . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ,
" enabled: false " ,
)
. unwrap ( ) ;
fs ::write (
directory_b . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ,
" enabled: true \n bundle_path: \" /app/etc/MindWorkAI/company-b.pem \" \n allowed_hosts: \" *.b.example.org \" " ,
)
. unwrap ( ) ;
let policy = load_external_http_custom_root_certificate_policy_from_directories ( & [
directory_a . path ( ) . to_path_buf ( ) ,
directory_b . path ( ) . to_path_buf ( ) ,
] ) ;
assert_eq! (
policy ,
ExternalHttpCustomRootCertificatePolicy {
is_configured : true ,
enabled : false ,
bundle_path : String ::new ( ) ,
allowed_hosts : String ::new ( ) ,
source_detail : policy_path ( directory_a . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ) ,
}
) ;
}
#[ test ]
fn load_external_http_custom_root_certificate_policy_skips_invalid_files ( ) {
let directory_a = tempdir ( ) . unwrap ( ) ;
let directory_b = tempdir ( ) . unwrap ( ) ;
fs ::write (
directory_a . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ,
" enabled: maybe \n bundle_path: \" /app/etc/MindWorkAI/ignored.pem \" " ,
)
. unwrap ( ) ;
fs ::write (
directory_b . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ,
" enabled: yes \n bundle_path: \" /app/etc/MindWorkAI/company-b.pem \" \n allowed_hosts: \" *.b.example.org,eri.b.example.org \" " ,
)
. unwrap ( ) ;
let policy = load_external_http_custom_root_certificate_policy_from_directories ( & [
directory_a . path ( ) . to_path_buf ( ) ,
directory_b . path ( ) . to_path_buf ( ) ,
] ) ;
assert_eq! (
policy ,
ExternalHttpCustomRootCertificatePolicy {
is_configured : true ,
enabled : true ,
bundle_path : String ::from ( " /app/etc/MindWorkAI/company-b.pem " ) ,
allowed_hosts : String ::from ( " *.b.example.org,eri.b.example.org " ) ,
source_detail : policy_path ( directory_b . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ) ,
}
) ;
}
#[ test ]
fn load_external_http_custom_root_certificate_policy_requires_enabled_key ( ) {
let directory = tempdir ( ) . unwrap ( ) ;
fs ::write (
directory . path ( ) . join ( " external_http_custom_root_certificates.yaml " ) ,
" bundle_path: \" /app/etc/MindWorkAI/company.pem \" \n allowed_hosts: \" *.example.org \" " ,
)
. unwrap ( ) ;
let policy = load_external_http_custom_root_certificate_policy_from_directories ( & [ directory . path ( ) . to_path_buf ( ) ] ) ;
assert_eq! ( policy , ExternalHttpCustomRootCertificatePolicy ::default ( ) ) ;
}
2026-03-31 11:02:59 +00:00
#[ test ]
fn load_policy_values_from_directories_ignores_invalid_and_incomplete_files ( ) {
let directory = tempdir ( ) . unwrap ( ) ;
fs ::write ( directory . path ( ) . join ( " config0.yaml " ) , " id [broken " ) . unwrap ( ) ;
fs ::write (
directory . path ( ) . join ( " config1.yaml " ) ,
" id: \" 9072b77d-ca81-40da-be6a-861da525ef7b \" " ,
)
. unwrap ( ) ;
let values = load_policy_values_from_directories ( & [ directory . path ( ) . to_path_buf ( ) ] ) ;
let source = parse_enterprise_source_values ( " policy files " , & values ) ;
assert! ( source . configs . is_empty ( ) ) ;
}
}