use serde_json::{Value, json};
use zebflow::pipeline::trace_capture::{CaptureLevel, TraceCaptureSettings};
fn limits(level: CaptureLevel) -> zebflow::pipeline::trace_capture::TraceCaptureLimits {
TraceCaptureSettings {
level: Some(level),
..Default::default()
}
.resolve(None)
}
#[test]
fn the_default_level_is_on_error() {
assert_eq!(CaptureLevel::default(), CaptureLevel::OnError);
assert_eq!(
TraceCaptureSettings::default().resolve(None).level,
CaptureLevel::OnError,
"an unconfigured project must get on-error, not full"
);
}
#[test]
fn a_pipeline_overrides_the_projects_level() {
let project = TraceCaptureSettings {
level: Some(CaptureLevel::None),
..Default::default()
};
let pipeline = TraceCaptureSettings {
level: Some(CaptureLevel::Full),
..Default::default()
};
assert_eq!(project.resolve(Some(&pipeline)).level, CaptureLevel::Full);
let silent = TraceCaptureSettings::default();
assert_eq!(project.resolve(Some(&silent)).level, CaptureLevel::None);
}
#[test]
fn levels_agree_on_what_they_record() {
let l = limits(CaptureLevel::None);
assert_eq!(l.level, CaptureLevel::None);
let l = limits(CaptureLevel::OnError);
assert_eq!(l.level, CaptureLevel::OnError);
let l = limits(CaptureLevel::Full);
assert_eq!(l.level, CaptureLevel::Full);
}
#[test]
fn a_level_does_not_change_the_budgets() {
let defaults = TraceCaptureSettings::default().resolve(None);
for level in [CaptureLevel::None, CaptureLevel::OnError, CaptureLevel::Full] {
let l = limits(level);
assert_eq!(l.max_node_bytes, defaults.max_node_bytes);
assert_eq!(l.max_run_bytes, defaults.max_run_bytes);
assert_eq!(l.max_depth, defaults.max_depth);
assert_eq!(l.array_sample_count, defaults.array_sample_count);
}
}
#[test]
fn the_level_serializes_as_an_operator_would_write_it() {
let parsed: TraceCaptureSettings =
serde_json::from_value(json!({ "level": "on-error" })).expect("on-error must parse");
assert_eq!(parsed.level, Some(CaptureLevel::OnError));
let parsed: TraceCaptureSettings =
serde_json::from_value(json!({ "level": "none" })).expect("none must parse");
assert_eq!(parsed.level, Some(CaptureLevel::None));
let written = serde_json::to_value(TraceCaptureSettings {
level: Some(CaptureLevel::Full),
..Default::default()
})
.expect("serialize");
assert_eq!(written.get("level"), Some(&Value::String("full".into())));
let written = serde_json::to_value(TraceCaptureSettings::default()).expect("serialize");
assert_eq!(written.get("level"), None);
}
use zebflow::platform::services::credential::{confidential_values, register_confidential_for_test};
#[test]
fn a_credential_value_is_masked_at_every_level() {
let owner = "rule3-owner";
let project = "rule3-project";
let secret = "sk-live-9f3c2ab7e41d8065-not-a-name-anyone-listed";
register_confidential_for_test(owner, project, &json!({ "anything": secret }));
let known = confidential_values(owner, project);
assert!(
known.iter().any(|v| v == secret),
"the value must be registered where it resolves"
);
for level in [CaptureLevel::None, CaptureLevel::OnError, CaptureLevel::Full] {
let captured = zebflow::pipeline::trace_capture::capture_for_test(
limits(level),
known.clone(),
&json!({ "code": secret, "note": format!("token was {secret}") }),
);
let text = serde_json::to_string(&captured).unwrap();
assert!(
!text.contains(secret),
"level {level:?} showed a credential value: {text}"
);
}
}
#[test]
fn short_ordinary_values_are_not_swept_up() {
let owner = "rule3-short";
let project = "rule3-short";
register_confidential_for_test(
owner,
project,
&json!({ "host": "db", "port": "5432", "tls": "true" }),
);
let known = confidential_values(owner, project);
assert!(
!known.iter().any(|v| v == "db" || v == "5432" || v == "true"),
"values below the length floor must not be registered: {known:?}"
);
}
#[test]
fn resolving_a_credential_registers_it_without_the_node_asking() {
use zebflow::platform::model::{PlatformConfig, UpsertProjectCredentialRequest};
use zebflow::platform::services::PlatformService;
let tmp = tempfile::tempdir().expect("tempdir");
let mut cfg = PlatformConfig::default();
cfg.data_root = tmp.path().join("platform");
let platform = PlatformService::from_config(cfg).expect("platform");
let secret = "smtp-password-3f9a2c71b45e8d06";
platform
.credentials
.upsert_project_credential(
"superadmin",
"default",
&UpsertProjectCredentialRequest {
credential_id: "relay".to_string(),
title: "Test relay".to_string(),
kind: "smtp".to_string(),
secret: json!({ "host": "127.0.0.1", "password": secret }),
notes: String::new(),
},
)
.expect("credential");
let before = confidential_values("superadmin", "default");
assert!(
!before.iter().any(|v| v == secret),
"storing a credential should not itself register it"
);
let resolved = platform
.credentials
.get_project_credential("superadmin", "default", "relay")
.expect("resolve")
.expect("present");
assert_eq!(resolved.credential_id, "relay");
let after = confidential_values("superadmin", "default");
assert!(
after.iter().any(|v| v == secret),
"resolving must register the secret: {after:?}"
);
let other = confidential_values("superadmin", "someone-else");
assert!(
!other.iter().any(|v| v == secret),
"a project must not see another project's secrets"
);
}
use zebflow::pipeline::trace_capture::mask_secret_paths;
#[test]
fn a_declared_path_is_masked_wherever_it_sits() {
let body = json!({
"response": {
"status": 200,
"body": { "access_token": "ya29.SECRET", "scope": "openid email" }
}
});
let masked = mask_secret_paths(&body, &["/response/body/access_token".to_string()]);
let text = serde_json::to_string(&masked).unwrap();
assert!(!text.contains("ya29.SECRET"), "{text}");
assert_eq!(masked["response"]["status"], 200);
assert_eq!(masked["response"]["body"]["scope"], "openid email");
}
#[test]
fn a_wildcard_matches_every_element_and_every_member() {
let rows = json!({ "items": [ { "token": "A-SECRET" }, { "token": "B-SECRET" } ] });
let masked = mask_secret_paths(&rows, &["/items/*/token".to_string()]);
let text = serde_json::to_string(&masked).unwrap();
assert!(!text.contains("A-SECRET") && !text.contains("B-SECRET"), "{text}");
let map = json!({ "users": { "u1": "KEY-ONE", "u2": "KEY-TWO" } });
let masked = mask_secret_paths(&map, &["/users/*".to_string()]);
let text = serde_json::to_string(&masked).unwrap();
assert!(!text.contains("KEY-ONE") && !text.contains("KEY-TWO"), "{text}");
}
#[test]
fn a_path_that_matches_nothing_changes_nothing() {
let payload = json!({ "a": 1, "b": { "c": "visible" } });
let masked = mask_secret_paths(&payload, &["/nope/missing".to_string()]);
assert_eq!(masked, payload);
}
#[test]
fn masking_a_subtree_hides_everything_under_it() {
let payload = json!({ "keep": "yes", "vault": { "deep": { "key": "BURIED-SECRET" } } });
let masked = mask_secret_paths(&payload, &["/vault".to_string()]);
let text = serde_json::to_string(&masked).unwrap();
assert!(!text.contains("BURIED-SECRET"), "{text}");
assert_eq!(masked["keep"], "yes");
}
#[test]
fn a_node_kind_can_declare_where_its_secret_sits() {
let def = zebflow::pipeline::nodes::builtin_node_definitions()
.into_iter()
.find(|d| d.kind == "auth.token.create")
.expect("auth.token.create must exist");
assert!(
def.secret_paths.iter().any(|p| p == "/token/access_token"),
"the token node must declare its token: {:?}",
def.secret_paths
);
}