use super::Fixture;
#[cfg(test)]
use super::FixtureDocs;
use crate::core::config::e2e::{SampleUrlManifest, SampleUrlTemplate, merge_manifest_vars};
use std::collections::BTreeMap;
impl Fixture {
pub fn docs_call_fixture(&self) -> Self {
self.docs_call_fixture_with_sample_base_url(crate::core::config::e2e::DEFAULT_DOCS_SAMPLE_BASE_URL)
}
pub fn docs_call_fixture_with_sample_base_url(&self, sample_base_url: &str) -> Self {
self.docs_call_fixture_with_sample_url(sample_base_url, None, None)
}
pub fn docs_call_fixture_with_sample_url(
&self,
sample_base_url: &str,
template: Option<&SampleUrlTemplate>,
manifest: Option<&SampleUrlManifest>,
) -> Self {
let mut fixture = self.clone();
if let Some(input) = self.docs.as_ref().and_then(|docs| docs.input.as_ref()) {
fixture.input = input.clone();
}
if let Some(presentation) = self.docs.as_ref().and_then(|docs| docs.presentation.as_ref()) {
if let Some(call) = &presentation.call {
fixture.call = Some(call.clone());
}
if let Some(input) = &presentation.input {
fixture.input = input.clone();
}
for file in &presentation.files {
if let Some(value) = fixture.input.pointer_mut(&file.field) {
*value = serde_json::Value::String(file.path.clone());
}
}
if let Some(args) = &presentation.args {
fixture.args = args.clone();
}
}
fixture.mock_response = None;
if let Some(input) = fixture.input.as_object_mut() {
input.remove("mock_responses");
}
let empty_vars = BTreeMap::new();
let fixture_vars = self.docs.as_ref().map_or(&empty_vars, |docs| &docs.sample_url_vars);
let body_file = self.docs.as_ref().and_then(|docs| docs.body_file.as_deref());
let vars = merge_manifest_vars(manifest, body_file, fixture_vars);
replace_docs_mock_urls(&mut fixture.input, sample_base_url, template, &vars);
fixture
}
}
fn replace_docs_mock_urls(
value: &mut serde_json::Value,
sample_base_url: &str,
template: Option<&SampleUrlTemplate>,
vars: &BTreeMap<String, String>,
) {
match value {
serde_json::Value::String(text) => {
if text.contains(crate::e2e::codegen::MOCK_URL_PLACEHOLDER) {
*text = resolve_mock_url_occurrences(text, sample_base_url, template, vars);
}
}
serde_json::Value::Array(values) => {
for value in values {
replace_docs_mock_urls(value, sample_base_url, template, vars);
}
}
serde_json::Value::Object(values) => {
for value in values.values_mut() {
replace_docs_mock_urls(value, sample_base_url, template, vars);
}
}
_ => {}
}
}
fn resolve_mock_url_occurrences(
text: &str,
sample_base_url: &str,
template: Option<&SampleUrlTemplate>,
vars: &BTreeMap<String, String>,
) -> String {
let placeholder = crate::e2e::codegen::MOCK_URL_PLACEHOLDER;
let mut result = String::with_capacity(text.len());
let mut rest = text;
while let Some(index) = rest.find(placeholder) {
result.push_str(&rest[..index]);
rest = &rest[index + placeholder.len()..];
let path_end = rest.find(placeholder).unwrap_or(rest.len());
let path = &rest[..path_end];
let resolved = crate::core::config::e2e::resolve_templated_sample_url(template, path, vars)
.unwrap_or_else(|| format!("{sample_base_url}{path}"));
result.push_str(&resolved);
rest = &rest[path_end..];
}
result.push_str(rest);
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_placeholder_is_replaced_at_every_depth_of_the_input() {
let fixture = Fixture {
input: serde_json::json!({
"url": "$mock_url/report.pdf",
"batch": ["$mock_url/a", "$mock_url/b"],
"nested": {"fallback": "$mock_url/c"},
}),
..Fixture::default()
};
let docs = fixture.docs_call_fixture_with_sample_base_url("https://samples.example.org");
assert_eq!(
docs.input,
serde_json::json!({
"url": "https://samples.example.org/report.pdf",
"batch": ["https://samples.example.org/a", "https://samples.example.org/b"],
"nested": {"fallback": "https://samples.example.org/c"},
})
);
}
#[test]
fn the_unconfigured_spelling_binds_the_reserved_documentation_domain() {
let fixture = Fixture {
input: serde_json::json!({"url": "$mock_url/report.pdf"}),
..Fixture::default()
};
let docs = fixture.docs_call_fixture();
assert_eq!(
docs.input.get("url").and_then(|value| value.as_str()),
Some("https://example.com/report.pdf")
);
}
#[test]
fn a_fixture_with_a_matching_sample_url_var_publishes_its_own_templated_address() {
let fixture = Fixture {
input: serde_json::json!({"url": "$mock_url/pdf/fake_memo.pdf"}),
docs: Some(FixtureDocs {
sample_url_vars: BTreeMap::from([("digest".to_string(), "9f86d081884c7d659a2feaa".to_string())]),
..fixture_docs("contract")
}),
..Fixture::default()
};
let template = SampleUrlTemplate::resolve(Some("https://cdn.example.org/objects/{digest}"))
.expect("valid template resolves")
.expect("a configured value produces a template");
let docs = fixture.docs_call_fixture_with_sample_url("https://samples.example.org", Some(&template), None);
assert_eq!(
docs.input.get("url").and_then(|value| value.as_str()),
Some("https://cdn.example.org/objects/9f86d081884c7d659a2feaa")
);
}
#[test]
fn a_fixture_with_no_sample_url_vars_falls_back_to_sample_base_url_unchanged() {
let fixture = Fixture {
input: serde_json::json!({"url": "$mock_url/pdf/fake_memo.pdf"}),
docs: Some(fixture_docs("contract")),
..Fixture::default()
};
let template = SampleUrlTemplate::resolve(Some("https://cdn.example.org/objects/{digest}"))
.expect("valid template resolves")
.expect("a configured value produces a template");
let docs = fixture.docs_call_fixture_with_sample_url("https://samples.example.org", Some(&template), None);
assert_eq!(
docs.input.get("url").and_then(|value| value.as_str()),
Some("https://samples.example.org/pdf/fake_memo.pdf"),
"a fixture that cannot satisfy the template's placeholders must keep publishing the \
flat sample_base_url address, which is what keeps the reserved-domain warning honest"
);
}
#[test]
fn a_fixture_whose_body_file_is_covered_by_the_manifest_publishes_the_manifest_derived_address() {
let directory = tempfile::tempdir().expect("temp dir");
std::fs::write(
directory.path().join("manifest.json"),
r#"{"pdf/fake_memo.pdf": "9f86d081884c7d659a2feaa"}"#,
)
.expect("write manifest");
let manifest_config = crate::core::config::e2e::SampleUrlManifestConfig {
path: "manifest.json".to_string(),
variable: "digest".to_string(),
};
let manifest = SampleUrlManifest::resolve(Some(&manifest_config), directory.path())
.expect("valid manifest resolves")
.expect("a configured value produces a manifest");
let fixture = Fixture {
input: serde_json::json!({"url": "$mock_url/pdf/fake_memo.pdf"}),
docs: Some(FixtureDocs {
body_file: Some("pdf/fake_memo.pdf".to_string()),
..fixture_docs("contract")
}),
..Fixture::default()
};
let template = SampleUrlTemplate::resolve(Some("https://cdn.example.org/objects/{digest}"))
.expect("valid template resolves")
.expect("a configured value produces a template");
let docs =
fixture.docs_call_fixture_with_sample_url("https://samples.example.org", Some(&template), Some(&manifest));
assert_eq!(
docs.input.get("url").and_then(|value| value.as_str()),
Some("https://cdn.example.org/objects/9f86d081884c7d659a2feaa")
);
}
#[test]
fn a_fixtures_own_sample_url_var_outranks_the_manifest_for_the_same_placeholder() {
let directory = tempfile::tempdir().expect("temp dir");
std::fs::write(
directory.path().join("manifest.json"),
r#"{"pdf/fake_memo.pdf": "from-manifest"}"#,
)
.expect("write manifest");
let manifest_config = crate::core::config::e2e::SampleUrlManifestConfig {
path: "manifest.json".to_string(),
variable: "digest".to_string(),
};
let manifest = SampleUrlManifest::resolve(Some(&manifest_config), directory.path())
.expect("valid manifest resolves")
.expect("a configured value produces a manifest");
let fixture = Fixture {
input: serde_json::json!({"url": "$mock_url/pdf/fake_memo.pdf"}),
docs: Some(FixtureDocs {
body_file: Some("pdf/fake_memo.pdf".to_string()),
sample_url_vars: BTreeMap::from([("digest".to_string(), "from-fixture".to_string())]),
..fixture_docs("contract")
}),
..Fixture::default()
};
let template = SampleUrlTemplate::resolve(Some("https://cdn.example.org/objects/{digest}"))
.expect("valid template resolves")
.expect("a configured value produces a template");
let docs =
fixture.docs_call_fixture_with_sample_url("https://samples.example.org", Some(&template), Some(&manifest));
assert_eq!(
docs.input.get("url").and_then(|value| value.as_str()),
Some("https://cdn.example.org/objects/from-fixture"),
"an explicit docs.sample_url_vars entry must win over the manifest for the same key"
);
}
fn fixture_docs(topic: &str) -> FixtureDocs {
FixtureDocs {
sample_url: None,
topic: topic.to_string(),
stem: None,
paths: Default::default(),
title: None,
description: None,
input: None,
shows: Vec::new(),
error: None,
presentation: None,
client: None,
side_effects: Default::default(),
coverage_exceptions: Default::default(),
sample_url_vars: Default::default(),
body_file: None,
}
}
}