Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions crates/trusted-server-core/src/cookies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ pub fn expire_ec_cookie(settings: &Settings, response: &mut fastly::Response) {

#[cfg(test)]
mod tests {
use fastly::http::HeaderValue;

use crate::test_support::tests::create_test_settings;

use super::*;
Expand Down Expand Up @@ -347,6 +349,28 @@ mod tests {
assert!(jar.iter().count() == 0);
}

#[test]
fn test_handle_request_cookies_invalid_utf8_header_value() {
let mut req = Request::get("http://example.com");
req.set_header(
header::COOKIE,
HeaderValue::from_bytes(b"session=\xFF")
.expect("should construct HeaderValue from non-UTF-8 bytes"),
);

let error = handle_request_cookies(&req).expect_err("should reject invalid UTF-8");
let trusted_server_error = error.current_context();

assert!(
matches!(
trusted_server_error,
TrustedServerError::InvalidHeaderValue { message }
if message == "Cookie header contains invalid UTF-8"
),
"should return InvalidHeaderValue for invalid UTF-8 cookie headers",
);
}
Comment thread
ChristianPavilonis marked this conversation as resolved.

#[test]
fn test_set_ec_cookie() {
let settings = create_test_settings();
Expand Down
128 changes: 128 additions & 0 deletions crates/trusted-server-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,136 @@ impl IntoHttpResponse for TrustedServerError {

#[cfg(test)]
mod tests {
use http::StatusCode;

use super::*;

#[test]
fn status_code_maps_each_error_variant_to_expected_http_response() {
// Compile-time guard: adding a TrustedServerError variant without
// updating this test will fail to compile.
let _exhaustive = |error: &TrustedServerError| match error {
TrustedServerError::BadRequest { .. }
| TrustedServerError::Configuration { .. }
| TrustedServerError::Auction { .. }
| TrustedServerError::Gam { .. }
| TrustedServerError::GdprConsent { .. }
| TrustedServerError::InvalidUtf8 { .. }
| TrustedServerError::InvalidHeaderValue { .. }
| TrustedServerError::KvStore { .. }
| TrustedServerError::Prebid { .. }
| TrustedServerError::Integration { .. }
| TrustedServerError::Proxy { .. }
| TrustedServerError::Forbidden { .. }
| TrustedServerError::AllowlistViolation { .. }
| TrustedServerError::Settings { .. }
| TrustedServerError::Ec { .. } => (),
};

let cases = [
(
TrustedServerError::BadRequest {
message: "missing field".into(),
},
StatusCode::BAD_REQUEST,
),
(
TrustedServerError::Configuration {
message: "invalid config".into(),
},
StatusCode::INTERNAL_SERVER_ERROR,
),
(
TrustedServerError::Auction {
message: "bid timeout".into(),
},
StatusCode::BAD_GATEWAY,
),
(
TrustedServerError::Gam {
message: "upstream error".into(),
},
StatusCode::BAD_GATEWAY,
),
(
TrustedServerError::GdprConsent {
message: "missing consent string".into(),
},
StatusCode::BAD_REQUEST,
),
(
TrustedServerError::InvalidUtf8 {
message: "byte 0xff".into(),
},
StatusCode::INTERNAL_SERVER_ERROR,
),
(
TrustedServerError::InvalidHeaderValue {
message: "non-ascii".into(),
},
StatusCode::BAD_REQUEST,
),
(
TrustedServerError::KvStore {
store_name: "users".into(),
message: "timeout".into(),
},
StatusCode::SERVICE_UNAVAILABLE,
),
(
TrustedServerError::Prebid {
message: "adapter error".into(),
},
StatusCode::BAD_GATEWAY,
),
(
TrustedServerError::Integration {
integration: "foo".into(),
message: "connection refused".into(),
},
StatusCode::BAD_GATEWAY,
),
(
TrustedServerError::Proxy {
message: "upstream refused".into(),
},
StatusCode::BAD_GATEWAY,
),
(
TrustedServerError::Forbidden {
message: "missing credentials".into(),
},
StatusCode::FORBIDDEN,
),
(
TrustedServerError::AllowlistViolation {
host: "blocked.example.com".into(),
},
StatusCode::FORBIDDEN,
),
(
TrustedServerError::Settings {
message: "parse failed".into(),
},
StatusCode::INTERNAL_SERVER_ERROR,
),
(
TrustedServerError::Ec {
message: "seed missing".into(),
},
StatusCode::INTERNAL_SERVER_ERROR,
),
];

for (error, expected_status) in cases {
assert_eq!(
error.status_code(),
expected_status,
"should map {error:?} to the expected HTTP status",
);
}
}
Comment thread
ChristianPavilonis marked this conversation as resolved.

#[test]
fn server_errors_return_generic_message() {
let cases = [
Expand Down
17 changes: 2 additions & 15 deletions crates/trusted-server-core/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,32 +55,19 @@ mod tests {
use serde_json::json;

#[test]
fn test_callback_deserialization() {
fn test_callback_deserialization_renames_type_field() {
let json_data = json!({
"type": "impression",
"url": "https://example.com/track/impression"
});

let callback: Callback =
serde_json::from_value(json_data).expect("should deserialize callback");

assert_eq!(callback.callback_type, "impression");
assert_eq!(callback.url, "https://example.com/track/impression");
}

#[test]
fn test_callback_type_field_rename() {
// Test that "type" is correctly renamed to callback_type
let json_str = r#"{
"type": "click",
"url": "https://example.com/track/click"
}"#;

let callback: Callback =
serde_json::from_str(json_str).expect("should deserialize callback from str");
assert_eq!(callback.callback_type, "click");
assert_eq!(callback.url, "https://example.com/track/click");
}

#[test]
fn test_ad_response_full_deserialization() {
let json_data = json!({
Expand Down
116 changes: 116 additions & 0 deletions crates/trusted-server-core/src/tsjs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,119 @@ pub fn tsjs_deferred_script_tags(module_ids: &[&str]) -> String {
.map(|id| tsjs_deferred_script_tag(id))
.collect::<String>()
}

#[cfg(test)]
mod tests {
use super::*;

fn assert_sha256_hex_hash(src: &str) {
let hash = src
.rsplit_once("?v=")
.expect("should have cache-busting query parameter")
.1;

assert_eq!(hash.len(), 64, "should use a SHA-256 hex hash");
assert!(
hash.chars().all(|character| character.is_ascii_hexdigit()),
"should use only ASCII hex digits",
);
}

#[test]
fn tsjs_script_src_formats_unified_bundle_url_with_hash() {
let creative_src = tsjs_script_src(&["creative"]);
let creative_prebid_src = tsjs_script_src(&["creative", "prebid"]);

assert!(
creative_src.starts_with("/static/tsjs=tsjs-unified.min.js?v="),
"should include the unified bundle path and cache-busting parameter",
);
assert_sha256_hex_hash(&creative_src);
assert_ne!(
creative_src, creative_prebid_src,
"should generate different cache-busting URLs for different module sets",
);
}
Comment thread
ChristianPavilonis marked this conversation as resolved.

#[test]
fn tsjs_script_tag_wraps_source_in_a_single_tag() {
let module_ids = ["creative"];
let expected_src = tsjs_script_src(&module_ids);

assert_eq!(
tsjs_script_tag(&module_ids),
format!("<script src=\"{expected_src}\" id=\"trustedserver-js\"></script>"),
"should render the injected trustedserver script tag",
);
}

#[test]
fn tsjs_unified_helpers_use_all_module_ids() {
Comment thread
ChristianPavilonis marked this conversation as resolved.
let unified = tsjs_unified_script_src();
let manual = tsjs_script_src(&all_module_ids());

assert_eq!(
unified, manual,
"unified helpers must hash every registered module",
);
assert!(
unified.starts_with("/static/tsjs=tsjs-unified.min.js?v="),
"should include the unified bundle path and cache-busting parameter",
);
assert_sha256_hex_hash(&unified);
assert_eq!(
tsjs_unified_script_tag(),
format!("<script src=\"{unified}\" id=\"trustedserver-js\"></script>"),
"should wrap the unified bundle source in the standard script tag",
);
}

#[test]
fn tsjs_deferred_helpers_format_single_module_urls_and_tags() {
let module_id = "prebid";
let src = tsjs_deferred_script_src(module_id);

assert!(
src.starts_with("/static/tsjs=tsjs-prebid.min.js?v="),
"should include the deferred module path and cache-busting parameter",
);
assert_sha256_hex_hash(&src);
assert_eq!(
tsjs_deferred_script_tag(module_id),
format!("<script src=\"{src}\" defer></script>"),
"should render a deferred script tag for the module",
);
}

#[test]
fn tsjs_deferred_script_src_documents_unregistered_module_fallback() {
assert_eq!(
tsjs_deferred_script_src("unknown-module"),
"/static/tsjs=tsjs-unknown-module.min.js?v=",
"should preserve the current fallback until callers validate registered modules",
);
}
Comment thread
ChristianPavilonis marked this conversation as resolved.

#[test]
fn tsjs_deferred_script_tags_concatenates_tags_in_input_order() {
let result = tsjs_deferred_script_tags(&["prebid", "creative"]);
let prebid_pos = result
.find("tsjs-prebid")
.expect("should contain prebid script");
let creative_pos = result
.find("tsjs-creative")
.expect("should contain creative script");

assert!(prebid_pos < creative_pos, "should preserve input order");
assert_eq!(
result.matches("<script ").count(),
2,
"should emit one tag per module",
);
assert_eq!(
result.matches(" defer></script>").count(),
2,
"should mark each deferred script tag with defer",
);
}
}
Loading