use super::*;
use crate::core::config::e2e::CallConfig;
use crate::core::ir::{FieldDef, FunctionDef, TypeDef, TypeRef};
fn docs_fixture(assertions: serde_json::Value) -> Fixture {
serde_json::from_value(serde_json::json!({
"id": "sample_fixture",
"description": "Sample fixture",
"input": {"html": "<p>Hello World</p>"},
"assertions": assertions,
"docs": {"topic": "smoke", "stem": "sample_fixture"}
}))
.expect("fixture must parse")
}
fn config() -> E2eConfig {
E2eConfig {
call: CallConfig {
function: "convert".into(),
result_var: "result".into(),
..CallConfig::default()
},
..E2eConfig::default()
}
}
fn field(name: &str, ty: TypeRef, optional: bool) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
optional,
..FieldDef::default()
}
}
fn convert_returning(type_name: &str) -> Vec<FunctionDef> {
vec![FunctionDef {
name: "convert".to_string(),
return_type: TypeRef::Named(type_name.to_string()),
..FunctionDef::default()
}]
}
fn metadata_type() -> TypeDef {
TypeDef {
name: "Metadata".to_string(),
fields: vec![field("title", TypeRef::String, false)],
..TypeDef::default()
}
}
#[test]
fn an_optional_field_on_the_calls_return_type_is_optional_despite_a_required_twin_elsewhere() {
let type_defs = vec![
TypeDef {
name: "ConversionResult".to_string(),
fields: vec![field(
"metadata",
TypeRef::Optional(Box::new(TypeRef::Named("Metadata".to_string()))),
true,
)],
..TypeDef::default()
},
TypeDef {
name: "LogRecord".to_string(),
fields: vec![field("metadata", TypeRef::Named("Metadata".to_string()), false)],
..TypeDef::default()
},
metadata_type(),
];
let fixture = docs_fixture(serde_json::json!([
{"type": "equals", "field": "metadata.title", "value": "Hello"}
]));
let operations = resolve(
&fixture,
&config(),
"node",
&type_defs,
&[],
&convert_returning("ConversionResult"),
);
assert_eq!(
operations
.iter()
.map(|operation| operation.expression.as_str())
.collect::<Vec<_>>(),
vec!["result.metadata?.title"],
"optionality must come from `metadata` on ConversionResult, not from a vote across every \
type that declares a field called `metadata`"
);
}
#[test]
fn a_required_field_of_a_default_deriving_result_type_is_optional_in_the_node_binding() {
let type_defs = vec![
TypeDef {
name: "ScrapeOutcome".to_string(),
has_default: true,
fields: vec![field("metadata", TypeRef::Named("Metadata".to_string()), false)],
..TypeDef::default()
},
metadata_type(),
];
let fixture = docs_fixture(serde_json::json!([
{"type": "equals", "field": "metadata.title", "value": "Hello"}
]));
let operations = resolve(
&fixture,
&config(),
"node",
&type_defs,
&[],
&convert_returning("ScrapeOutcome"),
);
assert_eq!(
operations
.iter()
.map(|operation| operation.expression.as_str())
.collect::<Vec<_>>(),
vec!["result.metadata?.title"],
"the NAPI binding declares every field of a Default-deriving type optional, so the \
snippet must reach through `metadata` with `?.`"
);
}
#[test]
fn a_required_field_of_a_default_deriving_result_type_stays_required_in_the_wasm_binding() {
let type_defs = vec![
TypeDef {
name: "ScrapeOutcome".to_string(),
has_default: true,
fields: vec![field("metadata", TypeRef::Named("Metadata".to_string()), false)],
..TypeDef::default()
},
metadata_type(),
];
let fixture = docs_fixture(serde_json::json!([
{"type": "equals", "field": "metadata.title", "value": "Hello"}
]));
let operations = resolve(
&fixture,
&config(),
"wasm",
&type_defs,
&[],
&convert_returning("ScrapeOutcome"),
);
assert_eq!(
operations
.iter()
.map(|operation| operation.expression.as_str())
.collect::<Vec<_>>(),
vec!["result.metadata.title"]
);
}
#[test]
fn a_field_declared_only_on_an_unrelated_type_derives_no_accessor() {
let type_defs = vec![
TypeDef {
name: "ConversionResult".to_string(),
fields: vec![field("content", TypeRef::String, false)],
..TypeDef::default()
},
TypeDef {
name: "ReportRecord".to_string(),
fields: vec![field("summary", TypeRef::String, false)],
..TypeDef::default()
},
];
let fixture = docs_fixture(serde_json::json!([
{"type": "equals", "field": "summary", "value": "Hello"}
]));
let operations = resolve(
&fixture,
&config(),
"python",
&type_defs,
&[],
&convert_returning("ConversionResult"),
);
assert!(
operations.is_empty(),
"`summary` is declared on ReportRecord, not on the type `convert` returns: {operations:?}"
);
}
#[test]
fn a_field_the_result_type_declares_still_derives_its_accessor() {
let type_defs = vec![
TypeDef {
name: "ConversionResult".to_string(),
fields: vec![field("content", TypeRef::String, false)],
..TypeDef::default()
},
TypeDef {
name: "ReportRecord".to_string(),
fields: vec![field("summary", TypeRef::String, false)],
..TypeDef::default()
},
];
let fixture = docs_fixture(serde_json::json!([
{"type": "equals", "field": "content", "value": "Hello"}
]));
let operations = resolve(
&fixture,
&config(),
"python",
&type_defs,
&[],
&convert_returning("ConversionResult"),
);
assert_eq!(
operations
.iter()
.map(|operation| operation.expression.as_str())
.collect::<Vec<_>>(),
vec!["result.content"]
);
}
#[test]
fn an_unresolvable_result_type_keeps_the_unanchored_answers() {
let type_defs = vec![
TypeDef {
name: "ConversionResult".to_string(),
fields: vec![field(
"metadata",
TypeRef::Optional(Box::new(TypeRef::Named("Metadata".to_string()))),
true,
)],
..TypeDef::default()
},
TypeDef {
name: "LogRecord".to_string(),
fields: vec![field("metadata", TypeRef::Named("Metadata".to_string()), false)],
..TypeDef::default()
},
TypeDef {
name: "ReportRecord".to_string(),
fields: vec![field("summary", TypeRef::String, false)],
..TypeDef::default()
},
metadata_type(),
];
let guarded = resolve(
&docs_fixture(serde_json::json!([{"type": "equals", "field": "metadata.title", "value": "Hello"}])),
&config(),
"node",
&type_defs,
&[],
&[],
);
assert_eq!(
guarded
.iter()
.map(|operation| operation.expression.as_str())
.collect::<Vec<_>>(),
vec!["result.metadata.title"],
"no resolvable result type means no anchored optionality"
);
let unrelated = resolve(
&docs_fixture(serde_json::json!([{"type": "equals", "field": "summary", "value": "Hello"}])),
&config(),
"python",
&type_defs,
&[],
&[],
);
assert_eq!(
unrelated
.iter()
.map(|operation| operation.expression.as_str())
.collect::<Vec<_>>(),
vec!["result.summary"],
"no resolvable result type means no anchored rejection either"
);
}
#[test]
fn the_availability_oracles_disagree_on_purpose_for_a_name_the_result_type_lacks() {
let type_defs = vec![
TypeDef {
name: "ConversionResult".to_string(),
fields: vec![field("content", TypeRef::String, false)],
..TypeDef::default()
},
TypeDef {
name: "ReportRecord".to_string(),
fields: vec![field("summary", TypeRef::String, false)],
..TypeDef::default()
},
];
let e2e_config = config();
let resolver = build_resolver(
&e2e_config,
&e2e_config.call,
"python",
&type_defs,
&[],
&convert_returning("ConversionResult"),
);
for absent in ["summary", "rate_limit.min_duration_ms"] {
assert!(
resolver.is_valid_for_result(absent),
"a hand-authored assertion on `{absent}` must still render"
);
assert_eq!(
resolver.result_field_oracle_knows(absent),
Some(false),
"an inferred accessor for `{absent}` must be refused"
);
}
assert!(resolver.is_valid_for_result("content"));
assert_eq!(resolver.result_field_oracle_knows("content"), Some(true));
}
#[test]
fn a_result_field_named_like_a_streaming_pseudo_field_still_derives_its_accessor() {
let type_defs = vec![
TypeDef {
name: "SegmentReport".to_string(),
fields: vec![
field(
"chunks",
TypeRef::Vec(Box::new(TypeRef::Named("Segment".to_string()))),
false,
),
field("total", TypeRef::String, false),
],
..TypeDef::default()
},
TypeDef {
name: "Segment".to_string(),
fields: vec![field("text", TypeRef::String, false)],
..TypeDef::default()
},
];
let assertions = serde_json::json!([
{"type": "length_greater_than", "field": "chunks", "value": 0},
{"type": "equals", "field": "total", "value": "3"}
]);
let operations = resolve(
&docs_fixture(assertions.clone()),
&config(),
"python",
&type_defs,
&[],
&convert_returning("SegmentReport"),
);
assert_eq!(
operations
.iter()
.map(|operation| operation.expression.as_str())
.collect::<Vec<_>>(),
vec!["result.chunks", "result.total"],
"`chunks` is declared by the type `convert` returns, so the snippet must show it"
);
let streaming: Fixture = serde_json::from_value(serde_json::json!({
"id": "streaming_fixture",
"description": "Streaming fixture",
"input": {"html": "<p>Hello</p>"},
"assertions": assertions,
"mock_response": {"status": 200, "stream_chunks": ["a", "b"]},
"docs": {"topic": "smoke", "stem": "streaming_fixture"}
}))
.expect("fixture must parse");
let streamed = resolve(
&streaming,
&config(),
"python",
&type_defs,
&[],
&convert_returning("SegmentReport"),
);
assert_eq!(
streamed
.iter()
.map(|operation| operation.expression.as_str())
.collect::<Vec<_>>(),
vec!["result.total"],
"on a streaming fixture `chunks` names the collected local list, not a result member"
);
}