Merge origin/main into fix/gemini-cli-v1internal

This commit is contained in:
Mas0nShi
2026-05-28 11:58:00 +08:00
410 changed files with 38026 additions and 6621 deletions
@@ -385,7 +385,7 @@ fn resolve_admin_monitoring_usage_candidate_id(
trace: &DecisionTrace,
usage: &StoredRequestUsageAudit,
) -> Option<String> {
if usage.request_id.trim() != trace.request_id {
if !admin_monitoring_usage_matches_trace(usage, trace.request_id.as_str()) {
return None;
}
@@ -413,6 +413,43 @@ fn resolve_admin_monitoring_usage_candidate_id(
.map(|item| item.candidate.id.clone())
}
fn admin_monitoring_usage_matches_trace(
usage: &StoredRequestUsageAudit,
trace_request_id: &str,
) -> bool {
let trace_request_id = trace_request_id.trim();
if trace_request_id.is_empty() {
return false;
}
usage.request_id.trim() == trace_request_id
|| usage
.trace_id()
.is_some_and(|value| value.trim() == trace_request_id)
|| admin_monitoring_headers_contain_trace_id(
usage.request_headers.as_ref(),
trace_request_id,
)
|| admin_monitoring_headers_contain_trace_id(
usage.provider_request_headers.as_ref(),
trace_request_id,
)
}
fn admin_monitoring_headers_contain_trace_id(
headers: Option<&Value>,
trace_request_id: &str,
) -> bool {
headers.and_then(Value::as_object).is_some_and(|object| {
object.iter().any(|(key, value)| {
key.eq_ignore_ascii_case("x-trace-id")
&& value
.as_str()
.map(str::trim)
.is_some_and(|value| value == trace_request_id)
})
})
}
fn admin_monitoring_candidate_status_rank(status: RequestCandidateStatus) -> u8 {
match status {
RequestCandidateStatus::Success => 7,
+87 -1
View File
@@ -1093,10 +1093,58 @@ fn infer_client_family_from_user_agent(user_agent: &str) -> Option<&'static str>
if normalized.contains("geminicli") || normalized.contains("gemini-cli") {
return Some("gemini_cli");
}
if normalized.contains("qwencode") {
return Some("qwen_code");
}
if normalized.contains("roo-code") || normalized.contains("roocode") {
return Some("roo_code");
}
if normalized.contains("kilo-code") || normalized.contains("kilocode") {
return Some("kilocode");
}
if normalized.contains("cherrystudio") || normalized.contains("cherry-studio") {
return Some("cherrystudio");
}
if normalized.contains("openui-agent-manager") || normalized.contains("openui") {
return Some("openui");
}
if normalized.contains("cursor") {
return Some("cursor");
}
if normalized.contains("windsurf") {
return Some("windsurf");
}
if normalized.contains("continue") {
return Some("continue");
}
if normalized.contains("cline") {
return Some("cline");
}
if normalized.contains("aider") {
return Some("aider");
}
if normalized.contains("langchain") {
return Some("langchain");
}
if normalized.contains("llamaindex") || normalized.contains("llama-index") {
return Some("llamaindex");
}
if normalized.starts_with("openai/js") {
return Some("openai_js_sdk");
}
None
if normalized.starts_with("openai/python") {
return Some("openai_python_sdk");
}
if normalized.starts_with("anthropic/js") || normalized.contains("anthropic-sdk-typescript") {
return Some("anthropic_js_sdk");
}
if normalized.starts_with("anthropic/python") || normalized.contains("anthropic-sdk-python") {
return Some("anthropic_python_sdk");
}
if normalized.contains("/js ") || normalized.contains("/python ") {
return Some("sdk");
}
Some("unknown")
}
pub fn admin_usage_client_family(item: &StoredRequestUsageAudit) -> Option<&str> {
@@ -1174,6 +1222,12 @@ fn admin_usage_active_request_json(
if let Some(target_model) = item.target_model.as_ref() {
value["target_model"] = json!(target_model);
}
if let Some(reasoning_effort) = item.provider_reasoning_effort() {
value["reasoning_effort"] = json!(reasoning_effort);
}
if let Some(service_tier) = item.provider_service_tier() {
value["service_tier"] = json!(service_tier);
}
if let Some(image_progress) = image_progress {
value["image_progress"] = image_progress.clone();
}
@@ -1287,6 +1341,12 @@ pub fn admin_usage_record_json(
"request_path_and_query",
admin_usage_metadata_string(item, "request_path_and_query"),
);
if let Some(reasoning_effort) = item.provider_reasoning_effort() {
object.insert("reasoning_effort".to_string(), json!(reasoning_effort));
}
if let Some(service_tier) = item.provider_service_tier() {
object.insert("service_tier".to_string(), json!(service_tier));
}
payload
}
@@ -2606,6 +2666,32 @@ mod tests {
assert_eq!(record["client_family"], "codex");
}
#[test]
fn admin_usage_record_includes_provider_reasoning_effort() {
let item = StoredRequestUsageAudit {
provider_request_body: Some(json!({
"reasoning": { "effort": "xhigh" },
"service_tier": "priority"
})),
..sample_usage("completed", Some(200), None)
};
let record = admin_usage_record_json(
&item,
&BTreeMap::new(),
&BTreeMap::new(),
false,
false,
None,
);
let active = admin_usage_active_request_json(&item, None, None, None);
assert_eq!(record["reasoning_effort"], "xhigh");
assert_eq!(active["reasoning_effort"], "xhigh");
assert_eq!(record["service_tier"], "priority");
assert_eq!(active["service_tier"], "priority");
}
#[test]
fn client_requested_stream_uses_stream_generate_content_path_over_stale_metadata_flag() {
let item = StoredRequestUsageAudit {
+21 -1
View File
@@ -440,7 +440,21 @@ pub fn admin_provider_ops_verify_headers(
)?;
}
}
"nekocode" | "done_hub" => {
"nekocode" => {
if let Some(session_cookie) = credentials
.get("session_cookie")
.and_then(Value::as_str)
.filter(|value| !value.trim().is_empty())
{
insert_header(
&mut headers,
"Cookie",
&admin_provider_ops_session_cookie_header(session_cookie),
)?;
}
}
"done_hub" => {
insert_header(&mut headers, "User-Agent", ADMIN_PROVIDER_OPS_USER_AGENT)?;
if let Some(session_cookie) = credentials
.get("session_cookie")
.and_then(Value::as_str)
@@ -1005,6 +1019,12 @@ mod tests {
headers.get(COOKIE).and_then(|value| value.to_str().ok()),
Some("session=abc")
);
assert_eq!(
headers
.get(USER_AGENT)
.and_then(|value| value.to_str().ok()),
Some(ADMIN_PROVIDER_OPS_USER_AGENT)
);
}
#[test]
+23 -2
View File
@@ -157,18 +157,39 @@ fn admin_pool_health_score(key: &StoredProviderCatalogKey) -> f64 {
}
fn admin_pool_circuit_breaker_open(key: &StoredProviderCatalogKey) -> bool {
let now_unix_secs = Utc::now().timestamp().max(0) as u64;
key.circuit_breaker_by_format
.as_ref()
.and_then(Value::as_object)
.map(|formats| {
formats
.values()
.filter_map(Value::as_object)
.any(|item| item.get("open").and_then(Value::as_bool).unwrap_or(false))
.any(|item| admin_pool_circuit_payload_active_open_at(item, now_unix_secs))
})
.unwrap_or(false)
}
fn admin_pool_circuit_payload_active_open_at(value: &Value, now_unix_secs: u64) -> bool {
let Some(item) = value.as_object() else {
return false;
};
if !item.get("open").and_then(Value::as_bool).unwrap_or(false) {
return false;
}
if let Some(next_probe_at) = item.get("next_probe_at_unix_secs").and_then(Value::as_u64) {
return now_unix_secs < next_probe_at;
}
if let Some(next_probe_at) = item
.get("next_probe_at")
.and_then(Value::as_str)
.and_then(|value| chrono::DateTime::parse_from_rfc3339(value).ok())
.and_then(|value| u64::try_from(value.timestamp()).ok())
{
return now_unix_secs < next_probe_at;
}
true
}
fn unix_secs_to_rfc3339(unix_secs: u64) -> Option<String> {
Utc.timestamp_opt(unix_secs as i64, 0)
.single()
+35 -1
View File
@@ -199,7 +199,7 @@ pub fn quota_refresh_success_invalid_state(
.as_deref()
.map(str::trim)
.unwrap_or_default();
if current_reason.starts_with(OAUTH_REFRESH_FAILED_PREFIX) {
if current_reason.starts_with(OAUTH_ACCOUNT_BLOCK_PREFIX) {
return (
key.oauth_invalid_at_unix_secs,
(!current_reason.is_empty()).then_some(current_reason.to_string()),
@@ -1724,6 +1724,7 @@ mod tests {
parse_codex_wham_usage_response, parse_gemini_cli_retrieve_user_quota_response,
parse_gemini_cli_v1internal_credits_response, parse_windsurf_model_configs_response,
parse_windsurf_rate_limit_response, parse_windsurf_user_status_response,
quota_refresh_success_invalid_state, should_auto_remove_structured_reason,
OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX, OAUTH_REFRESH_FAILED_PREFIX,
OAUTH_REQUEST_FAILED_PREFIX,
};
@@ -1846,6 +1847,13 @@ mod tests {
);
}
#[test]
fn auto_remove_structured_reason_keeps_oauth_expired_token_invalid() {
assert!(!should_auto_remove_structured_reason(Some(
"[OAUTH_EXPIRED] token invalidated"
)));
}
#[test]
fn auto_remove_refresh_failed_after_access_token_expiry() {
let mut key = StoredProviderCatalogKey::new(
@@ -1996,6 +2004,32 @@ mod tests {
));
}
#[test]
fn quota_refresh_success_clears_refresh_failed_marker() {
let mut key = StoredProviderCatalogKey::new(
"key-1".to_string(),
"provider-1".to_string(),
"key-1".to_string(),
"oauth".to_string(),
None,
true,
)
.expect("key should build");
key.oauth_invalid_reason = Some("[REFRESH_FAILED] Token 续期失败".to_string());
assert_eq!(quota_refresh_success_invalid_state(&key), (None, None));
}
#[test]
fn auto_remove_structured_reason_keeps_request_and_refresh_failures() {
assert!(!should_auto_remove_structured_reason(Some(
"[REQUEST_FAILED] 账号状态检查失败"
)));
assert!(!should_auto_remove_structured_reason(Some(
"[REFRESH_FAILED] Token 续期失败 (401): refresh_token 已失效"
)));
}
#[test]
fn parses_codex_spark_quota_from_additional_rate_limits() {
let parsed = parse_codex_wham_usage_response(
@@ -644,6 +644,19 @@ mod tests {
assert!(!snapshot.recoverable);
}
#[test]
fn oauth_expired_state_is_not_auto_removed() {
let state = resolve_pool_account_state(
Some("codex"),
None,
Some("[OAUTH_EXPIRED] token invalidated"),
);
assert!(state.blocked);
assert_eq!(state.code.as_deref(), Some("oauth_token_invalid"));
assert!(!should_auto_remove_account_state(&state));
}
#[test]
fn account_snapshot_detects_account_block_and_verification() {
let snapshot = resolve_account_status_snapshot(
+225 -16
View File
@@ -43,12 +43,12 @@ pub struct AdminEmailTemplateUpdate {
pub html: Option<String>,
}
pub const ADMIN_SYSTEM_CONFIG_EXPORT_VERSION: &str = "2.2";
pub const ADMIN_SYSTEM_CONFIG_EXPORT_VERSION: &str = "2.3";
pub const ADMIN_SYSTEM_CONFIG_SUPPORTED_VERSIONS: &[&str] =
&["2.0", "2.1", ADMIN_SYSTEM_CONFIG_EXPORT_VERSION];
pub const ADMIN_SYSTEM_USERS_EXPORT_VERSION: &str = "1.4";
&["2.0", "2.1", "2.2", ADMIN_SYSTEM_CONFIG_EXPORT_VERSION];
pub const ADMIN_SYSTEM_USERS_EXPORT_VERSION: &str = "1.5";
pub const ADMIN_SYSTEM_USERS_SUPPORTED_VERSIONS: &[&str] =
&["1.3", ADMIN_SYSTEM_USERS_EXPORT_VERSION];
&["1.3", "1.4", ADMIN_SYSTEM_USERS_EXPORT_VERSION];
pub const ADMIN_SYSTEM_PROVIDER_OPS_SENSITIVE_CREDENTIAL_FIELDS: &[&str] = &[
"api_key",
"password",
@@ -67,6 +67,8 @@ pub struct AdminSystemUpdateRelease {
pub release_url: Option<String>,
pub release_notes: Option<String>,
pub published_at: Option<String>,
pub tarball_url: Option<String>,
pub sha256sums_url: Option<String>,
}
fn default_true() -> bool {
@@ -269,6 +271,28 @@ where
}
}
fn deserialize_optional_u64_from_number<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = Option::<Value>::deserialize(deserializer)?;
match value {
None | Some(Value::Null) => Ok(None),
Some(Value::Number(number)) => number
.as_u64()
.map(Some)
.ok_or_else(|| de::Error::custom("expected a non-negative integer or numeric string")),
Some(Value::String(raw)) if !raw.trim().is_empty() => raw
.trim()
.parse::<u64>()
.map(Some)
.map_err(|_| de::Error::custom("expected a non-negative integer or numeric string")),
Some(_) => Err(de::Error::custom(
"expected a non-negative integer or numeric string",
)),
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AdminImportMergeMode {
@@ -345,6 +369,8 @@ pub struct AdminSystemConfigImportStats {
pub struct AdminSystemConfigGlobalModel {
pub name: String,
pub display_name: String,
#[serde(default, deserialize_with = "deserialize_optional_u64_from_number")]
pub usage_count: Option<u64>,
#[serde(default, deserialize_with = "deserialize_optional_f64_from_number")]
pub default_price_per_request: Option<f64>,
#[serde(default)]
@@ -709,6 +735,7 @@ const DEFAULT_BARK_API_BASE: &str = "https://api.day.app";
const SENSITIVE_SYSTEM_CONFIG_KEYS: &[&str] = &[
"smtp_password",
"turnstile_secret_key",
"backup_s3_secret_access_key",
"module.server_chan_push.send_key",
"module.important_notification.server_chan_send_key",
"module.bark_push.device_key",
@@ -826,11 +853,19 @@ pub fn build_admin_system_check_update_payload_with_release(
let has_update = latest_release
.as_ref()
.is_some_and(|release| admin_system_update_available(&current_version, &release.version));
let update_blocker = latest_release
.as_ref()
.and_then(admin_system_update_blocker);
let updatable = latest_release
.as_ref()
.is_some_and(|release| admin_system_update_blocker(release).is_none());
json!({
"current_version": current_version,
"latest_version": latest_release.as_ref().map(|release| release.version.clone()),
"has_update": has_update,
"updatable": updatable,
"update_blocker": update_blocker,
"release_url": latest_release.as_ref().and_then(|release| release.release_url.clone()),
"release_notes": latest_release.as_ref().and_then(|release| release.release_notes.clone()),
"published_at": latest_release.as_ref().and_then(|release| release.published_at.clone()),
@@ -838,6 +873,53 @@ pub fn build_admin_system_check_update_payload_with_release(
})
}
pub fn build_admin_system_releases_payload(
current_version: String,
releases: Vec<AdminSystemUpdateRelease>,
error: Option<String>,
) -> serde_json::Value {
let entries: Vec<serde_json::Value> = releases
.iter()
.map(|release| {
let is_current = {
let norm_current = normalized_admin_system_version(&current_version);
let norm_release = normalized_admin_system_version(&release.version);
norm_current == norm_release
};
let is_newer = admin_system_update_available(&current_version, &release.version);
let update_blocker = admin_system_update_blocker(release);
json!({
"version": release.version,
"release_url": release.release_url,
"release_notes": release.release_notes,
"published_at": release.published_at,
"tarball_url": release.tarball_url,
"sha256sums_url": release.sha256sums_url,
"is_current": is_current,
"is_newer": is_newer,
"updatable": update_blocker.is_none(),
"update_blocker": update_blocker,
})
})
.collect();
json!({
"current_version": current_version,
"releases": entries,
"error": error,
})
}
fn admin_system_update_blocker(release: &AdminSystemUpdateRelease) -> Option<&'static str> {
if release.tarball_url.is_none() {
Some("当前平台暂无安装包")
} else if release.sha256sums_url.is_none() {
Some("缺少 SHA256SUMS 校验文件")
} else {
None
}
}
fn normalized_admin_system_version(version: &str) -> String {
let trimmed = version.trim();
trimmed
@@ -1202,15 +1284,31 @@ pub fn ldap_module_config_is_valid(config: Option<&StoredLdapModuleConfig>) -> b
.is_some()
}
pub struct AdminModuleValidationInput<'a> {
pub module_name: &'a str,
pub oauth_providers: &'a [StoredOAuthProviderModuleConfig],
pub ldap_config: Option<&'a StoredLdapModuleConfig>,
pub gemini_files_has_capable_key: bool,
pub important_notification_configured: bool,
pub server_chan_push_configured: bool,
pub bark_push_configured: bool,
pub s3_backup_configured: bool,
}
pub fn build_admin_module_validation_result(
module_name: &str,
oauth_providers: &[StoredOAuthProviderModuleConfig],
ldap_config: Option<&StoredLdapModuleConfig>,
gemini_files_has_capable_key: bool,
important_notification_configured: bool,
server_chan_push_configured: bool,
bark_push_configured: bool,
input: AdminModuleValidationInput<'_>,
) -> (bool, Option<String>) {
let AdminModuleValidationInput {
module_name,
oauth_providers,
ldap_config,
gemini_files_has_capable_key,
important_notification_configured,
server_chan_push_configured,
bark_push_configured,
s3_backup_configured,
} = input;
match module_name {
"oauth" => {
if oauth_providers.is_empty() {
@@ -1301,6 +1399,13 @@ pub fn build_admin_module_validation_result(
(false, Some("请先配置 Bark Device Key".to_string()))
}
}
"s3_backup" => {
if s3_backup_configured {
(true, None)
} else {
(false, Some("请先完成 S3 备份配置".to_string()))
}
}
"gemini_files" => {
if gemini_files_has_capable_key {
(true, None)
@@ -1326,7 +1431,8 @@ pub fn build_admin_module_health(
| "proxy_nodes"
| "important_notification"
| "bark_push"
| "server_chan_push" => "healthy",
| "server_chan_push"
| "s3_backup" => "healthy",
"gemini_files" => {
if gemini_files_has_capable_key {
"healthy"
@@ -1590,6 +1696,24 @@ pub fn admin_system_config_default_value(key: &str) -> Option<serde_json::Value>
"turnstile_site_key" => Some(serde_json::Value::Null),
"turnstile_secret_key" => Some(serde_json::Value::Null),
"turnstile_allowed_hostnames" => Some(json!([])),
"backup_s3_enabled" => Some(json!(false)),
"backup_s3_scope" => Some(json!("data")),
"backup_s3_endpoint" => Some(serde_json::Value::Null),
"backup_s3_region" => Some(json!("auto")),
"backup_s3_bucket" => Some(serde_json::Value::Null),
"backup_s3_prefix" => Some(json!("aether/backups/")),
"backup_s3_access_key_id" => Some(serde_json::Value::Null),
"backup_s3_secret_access_key" => Some(serde_json::Value::Null),
"backup_s3_path_style" => Some(json!(true)),
"backup_s3_compression" => Some(json!("zstd")),
"backup_s3_schedule_unit" => Some(json!("days")),
"backup_s3_schedule_interval" => Some(json!(1)),
"backup_s3_schedule_minute" => Some(json!(0)),
"backup_s3_schedule_hour" => Some(json!(3)),
"backup_s3_schedule_weekday" => Some(json!(1)),
"backup_s3_schedule_month_day" => Some(json!(1)),
"backup_s3_retention_count" => Some(json!(7)),
"backup_s3_last_slot" => Some(serde_json::Value::Null),
"email_suffix_mode" => Some(json!("none")),
"email_suffix_list" => Some(json!([])),
"enable_format_conversion" => Some(json!(false)),
@@ -1605,7 +1729,8 @@ pub fn admin_system_config_default_value(key: &str) -> Option<serde_json::Value>
"medium": { "reasoning_effort": "medium" },
"high": { "reasoning_effort": "high" },
"xhigh": { "reasoning_effort": "xhigh" },
"max": { "reasoning_effort": "xhigh" }
"max": { "reasoning_effort": "xhigh" },
"fast": { "service_tier": "priority" }
}
},
"openai:responses": {
@@ -1615,7 +1740,8 @@ pub fn admin_system_config_default_value(key: &str) -> Option<serde_json::Value>
"medium": { "reasoning": { "effort": "medium" } },
"high": { "reasoning": { "effort": "high" } },
"xhigh": { "reasoning": { "effort": "xhigh" } },
"max": { "reasoning": { "effort": "xhigh" } }
"max": { "reasoning": { "effort": "xhigh" } },
"fast": { "service_tier": "priority" }
}
},
"openai:responses:compact": {
@@ -1625,7 +1751,8 @@ pub fn admin_system_config_default_value(key: &str) -> Option<serde_json::Value>
"medium": { "reasoning": { "effort": "medium" } },
"high": { "reasoning": { "effort": "high" } },
"xhigh": { "reasoning": { "effort": "xhigh" } },
"max": { "reasoning": { "effort": "xhigh" } }
"max": { "reasoning": { "effort": "xhigh" } },
"fast": { "service_tier": "priority" }
}
},
"claude:messages": {
@@ -2950,6 +3077,8 @@ mod tests {
),
release_notes: Some("release notes".to_string()),
published_at: Some("2026-05-13T00:00:00Z".to_string()),
tarball_url: None,
sha256sums_url: None,
}),
None,
);
@@ -2963,9 +3092,31 @@ mod tests {
);
assert_eq!(payload["release_notes"], "release notes");
assert_eq!(payload["published_at"], "2026-05-13T00:00:00Z");
assert_eq!(payload["updatable"], false);
assert_eq!(payload["update_blocker"], "当前平台暂无安装包");
assert_eq!(payload["error"], serde_json::Value::Null);
}
#[test]
fn build_admin_system_check_update_payload_reports_updatable_release() {
let payload = build_admin_system_check_update_payload_with_release(
"0.7.0-rc27".to_string(),
Some(AdminSystemUpdateRelease {
version: "v0.7.0-rc28".to_string(),
release_url: None,
release_notes: None,
published_at: None,
tarball_url: Some("https://github.com/fawney19/Aether/releases/download/v0.7.0-rc28/aether.tar.gz".to_string()),
sha256sums_url: Some("https://github.com/fawney19/Aether/releases/download/v0.7.0-rc28/SHA256SUMS".to_string()),
}),
None,
);
assert_eq!(payload["has_update"], true);
assert_eq!(payload["updatable"], true);
assert_eq!(payload["update_blocker"], serde_json::Value::Null);
}
#[test]
fn build_admin_system_check_update_payload_normalizes_v_prefix() {
let payload = build_admin_system_check_update_payload_with_release(
@@ -2975,6 +3126,8 @@ mod tests {
release_url: None,
release_notes: None,
published_at: None,
tarball_url: None,
sha256sums_url: None,
}),
None,
);
@@ -2992,6 +3145,8 @@ mod tests {
release_url: None,
release_notes: None,
published_at: None,
tarball_url: None,
sha256sums_url: None,
}),
None,
);
@@ -3009,6 +3164,8 @@ mod tests {
release_url: None,
release_notes: None,
published_at: None,
tarball_url: None,
sha256sums_url: None,
}),
None,
);
@@ -3026,6 +3183,8 @@ mod tests {
release_url: None,
release_notes: None,
published_at: None,
tarball_url: None,
sha256sums_url: None,
}),
None,
);
@@ -3058,7 +3217,7 @@ mod tests {
#[test]
fn parse_admin_system_config_import_request_rejects_unknown_versions() {
for version in ["1.9", "2.3"] {
for version in ["1.9", "2.4"] {
let err = parse_admin_system_config_import_request(
json!({
"version": version,
@@ -3134,6 +3293,7 @@ mod tests {
"global_models": [{
"name": "veo3.1",
"display_name": "Veo 3.1",
"usage_count": "42",
"default_price_per_request": "1.80000000",
}],
"providers": [{
@@ -3154,6 +3314,7 @@ mod tests {
.expect("numeric string fields from Python exports should parse");
let global_model = &parsed.request.document.global_models[0];
assert_eq!(global_model.usage_count, Some(42));
assert_eq!(global_model.default_price_per_request, Some(1.8));
let provider = &parsed.request.document.providers[0];
@@ -3215,6 +3376,41 @@ mod tests {
assert!(!is_sensitive_admin_system_config_key("site_name"));
}
#[test]
fn s3_backup_secret_access_key_is_sensitive() {
assert!(is_sensitive_admin_system_config_key(
"backup_s3_secret_access_key"
));
assert!(is_sensitive_admin_system_config_key(
"BACKUP_S3_SECRET_ACCESS_KEY"
));
assert!(!is_sensitive_admin_system_config_key("backup_s3_bucket"));
}
#[test]
fn s3_backup_defaults_match_admin_ui_contract() {
assert_eq!(
admin_system_config_default_value("backup_s3_scope"),
Some(json!("data"))
);
assert_eq!(
admin_system_config_default_value("backup_s3_schedule_unit"),
Some(json!("days"))
);
assert_eq!(
admin_system_config_default_value("backup_s3_schedule_interval"),
Some(json!(1))
);
assert_eq!(
admin_system_config_default_value("backup_s3_retention_count"),
Some(json!(7))
);
assert_eq!(
admin_system_config_default_value("backup_s3_path_style"),
Some(json!(true))
);
}
#[test]
fn legacy_notification_email_config_key_normalizes_to_important_notification() {
assert_eq!(
@@ -3230,6 +3426,19 @@ mod tests {
);
}
#[test]
fn s3_backup_secret_detail_is_write_only() {
let payload = build_admin_system_config_detail_payload(
"backup_s3_secret_access_key",
Some(json!("encrypted-secret")),
)
.expect("sensitive backup key should render");
assert_eq!(payload["key"], json!("backup_s3_secret_access_key"));
assert_eq!(payload["value"], serde_json::Value::Null);
assert_eq!(payload["is_set"], json!(true));
}
#[test]
fn legacy_server_chan_config_keys_normalize_to_push_module() {
assert_eq!(