use crate::handlers::pxcredentials_intelligence::*;
use crate::pxconfig::{PXConfig, PXCredentialEndpointConfig, PXPreparedCredentialEndpoint, PXRawCredentials};
use crate::pxcontext::PXContext;
#[test]
fn raw_credentials_empty_strings_become_none() {
let raw = PXRawCredentials {
user: Some(String::new()),
pass: Some(String::new()),
}
.without_empty_fields();
assert!(raw.user.is_none());
assert!(raw.pass.is_none());
}
#[test]
fn normalize_email_gmail_plus_and_dots() {
assert_eq!(
normalize_username("Perimeter.X+001@gmail.com"),
"perimeterx@gmail.com"
);
}
#[test]
fn hash_v2_gmail_user_credentials() {
let raw = PXRawCredentials {
user: Some("Perimeter.X+001@gmail.com".to_owned()),
pass: Some("1234".to_owned()),
};
let Some(hashed) = hash_v2(&raw) else {
return;
};
assert_eq!(
hashed.user.as_deref(),
Some("2bd9bd06f3440c682044a3f1b1fa7a97bd8b568a6e9e7d2cb0c6e858d9c78069")
);
assert_eq!(
hashed.pass.as_deref(),
Some("5246d99e5d2506d70db44e8216aecb7be42bf5bf7bc1766a680cbdad2ce046ab")
);
}
#[test]
fn normalize_non_email_unchanged() {
assert_eq!(normalize_username("pxUser"), "pxUser");
}
#[test]
fn hash_v2_regular_credentials() {
let raw = PXRawCredentials {
user: Some("pxUser".to_owned()),
pass: Some("1234".to_owned()),
};
let hashed = match hash_v2(&raw) {
Some(h) => h,
None => return,
};
assert_eq!(hashed.ci_version, CI_VERSION_V2);
assert_eq!(
hashed.user.as_deref(),
Some("9620f4cab3b3a50b9cbcb9a8d01328874ec33eb6882ae31c022f6986fc516851")
);
assert_eq!(
hashed.pass.as_deref(),
Some("c958c33151f273c620ec658ac4de9abd33ad7627df5d8c468224b0bae7173eb4")
);
}
#[test]
fn parse_urlencoded_decodes_percent_encoding() {
let map = parse_urlencoded("username=User%2eName&password=p%40ss");
assert_eq!(map.get("username").map(String::as_str), Some("User.Name"));
assert_eq!(map.get("password").map(String::as_str), Some("p@ss"));
}
#[test]
fn parse_multipart_boundary_handles_quoted_boundary() {
let content_type = r#"multipart/form-data; boundary="----WebKitFormBoundaryPxTest""#;
assert_eq!(
parse_multipart_boundary(content_type).as_deref(),
Some("----WebKitFormBoundaryPxTest")
);
}
#[test]
fn parse_multipart_boundary_handles_unquoted_boundary() {
let content_type = "multipart/form-data; boundary=----WebKitFormBoundaryPxTest";
assert_eq!(
parse_multipart_boundary(content_type).as_deref(),
Some("----WebKitFormBoundaryPxTest")
);
}
#[test]
fn multipart_extraction_works_with_quoted_boundary_header_value() {
let content_type = r#"multipart/form-data; boundary="----WebKitFormBoundaryPxTest""#;
let Some(boundary) = parse_multipart_boundary(content_type) else {
return;
};
let body = concat!(
"------WebKitFormBoundaryPxTest\r\n",
"Content-Disposition: form-data; name=\"username\"\r\n",
"\r\n",
"pxUser\r\n",
"------WebKitFormBoundaryPxTest\r\n",
"Content-Disposition: form-data; name=\"password\"\r\n",
"\r\n",
"1234\r\n",
"------WebKitFormBoundaryPxTest--\r\n"
);
let config = PXCredentialEndpointConfig {
user_field: "username".to_owned(),
pass_field: "password".to_owned(),
..Default::default()
};
let Some(raw) = extract_from_multipart(body, boundary, &config) else {
return;
};
assert_eq!(raw.user.as_deref(), Some("pxUser"));
assert_eq!(raw.pass.as_deref(), Some("1234"));
}
#[test]
fn hash_credentials_v1_is_unsupported() {
let raw = PXRawCredentials {
user: Some("u".to_owned()),
pass: Some("p".to_owned()),
};
assert!(hash_credentials(CI_VERSION_V1, &raw).is_none());
}
#[test]
fn hash_credentials_both_uses_multistep_for_partial() {
let raw = PXRawCredentials {
user: Some("pxUser".to_owned()),
pass: None,
};
let Some(hashed) = hash_credentials(CI_VERSION_BOTH, &raw) else {
return;
};
assert_eq!(hashed.ci_version, CI_VERSION_MULTISTEP);
assert_eq!(hashed.sso_step.as_deref(), Some(SSO_STEP_USER));
}
#[test]
fn endpoint_exact_match_is_case_insensitive_method() {
let endpoint = PXPreparedCredentialEndpoint {
config: PXCredentialEndpointConfig {
path: "/login".to_owned(),
method: "post".to_owned(),
..Default::default()
},
path_regex: None,
};
assert!(endpoint_matches(&endpoint, "/login", "POST"));
assert!(!endpoint_matches(&endpoint, "/login", "GET"));
}
#[test]
fn additional_s2s_base_uses_expected_schema_shape() {
let conf = PXConfig {
app_id: "PX123".to_owned(),
..Default::default()
};
let ctx = PXContext {
credential_intelligence: Some(PXCredentialIntelligenceData {
ci_version: CI_VERSION_V2.to_owned(),
raw_username: Some("pxUser".to_owned()),
..Default::default()
}),
uuid: Some("client-uuid".to_owned()),
full_url: "https://example.com/login".to_owned(),
ip: "1.1.1.1".to_owned(),
..Default::default()
};
let activity = build_additional_s2s_activity_base(&conf, &ctx);
assert_eq!(
activity.get("type").and_then(serde_json::Value::as_str),
Some("additional_s2s")
);
assert!(activity.get("headers").is_none());
let details = activity
.get("details")
.and_then(serde_json::Value::as_object);
assert_eq!(
details
.and_then(|d| d.get("client_uuid"))
.and_then(serde_json::Value::as_str),
Some("client-uuid")
);
assert_eq!(
details
.and_then(|d| d.get("ci_version"))
.and_then(serde_json::Value::as_str),
Some(CI_VERSION_V2)
);
}