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 allow non-UTF-8 cookie header 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",
);
}

#[test]
fn test_set_ec_cookie() {
let settings = create_test_settings();
Expand Down
108 changes: 108 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,116 @@ 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() {
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",
);
}
}

#[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
93 changes: 93 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,96 @@ pub fn tsjs_deferred_script_tags(module_ids: &[&str]) -> String {
.map(|id| tsjs_deferred_script_tag(id))
.collect::<String>()
}

#[cfg(test)]
mod tests {
use trusted_server_js::{all_module_ids, concatenated_hash, single_module_hash};

use super::*;

#[test]
fn tsjs_script_src_formats_unified_bundle_url_with_hash() {
let module_ids = ["core", "creative"];
let expected_hash = concatenated_hash(&module_ids);

assert_eq!(
tsjs_script_src(&module_ids),
format!("/static/tsjs=tsjs-unified.min.js?v={expected_hash}"),
"should include the unified bundle path and cache-busting hash",
);
}

#[test]
fn tsjs_script_tag_wraps_source_in_a_single_tag() {
let module_ids = ["core", "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() {
let all_ids = all_module_ids();
let expected_src = format!(
"/static/tsjs=tsjs-unified.min.js?v={}",
concatenated_hash(&all_ids)
);

assert_eq!(
tsjs_unified_script_src(),
expected_src,
"should hash all compiled modules for the unified bundle",
);
assert_eq!(
tsjs_unified_script_tag(),
format!("<script src=\"{expected_src}\" 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 expected_hash = single_module_hash(module_id).expect("should hash known module");
let expected_src = format!("/static/tsjs=tsjs-{module_id}.min.js?v={expected_hash}");

assert_eq!(
tsjs_deferred_script_src(module_id),
expected_src,
"should include the deferred module path and hash",
);
assert_eq!(
tsjs_deferred_script_tag(module_id),
format!("<script src=\"{expected_src}\" defer></script>"),
"should render a deferred script tag for the module",
);
}

#[test]
fn tsjs_deferred_script_src_uses_empty_hash_for_unknown_module() {
assert_eq!(
tsjs_deferred_script_src("unknown-module"),
"/static/tsjs=tsjs-unknown-module.min.js?v=",
"should fall back to an empty hash for unknown deferred modules",
);
}

#[test]
fn tsjs_deferred_script_tags_concatenates_tags_in_input_order() {
let module_ids = ["prebid", "creative"];
let expected = module_ids
.iter()
.map(|id| tsjs_deferred_script_tag(id))
.collect::<String>();

assert_eq!(
tsjs_deferred_script_tags(&module_ids),
expected,
"should concatenate one deferred script tag per module in order",
);
}
}
Loading