use super::*;
use crate::core::config::ResolvedCrateConfig;
use crate::core::config::e2e::StreamingConfig;
use crate::core::config::extras::{AdapterConfig, AdapterPattern};
use crate::e2e::config::{CallConfig, CallOverride, E2eConfig};
use crate::e2e::fixture::Fixture;
fn line_containing<'a>(body: &'a str, needle: &str) -> &'a str {
body.lines()
.find(|line| line.contains(needle))
.unwrap_or_else(|| panic!("no line contains {needle} in:\n{body}"))
}
fn engine_handle_arg() -> crate::e2e::config::ArgMapping {
crate::e2e::config::ArgMapping {
name: "engine".into(),
field: "input.engine".into(),
arg_type: "handle".into(),
optional: false,
owned: false,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: None,
}
}
fn url_arg() -> crate::e2e::config::ArgMapping {
crate::e2e::config::ArgMapping {
name: "url".into(),
field: "input.url".into(),
arg_type: "mock_url".into(),
optional: false,
owned: false,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: None,
}
}
fn urls_arg() -> crate::e2e::config::ArgMapping {
crate::e2e::config::ArgMapping {
name: "urls".into(),
field: "input.urls".into(),
arg_type: "mock_url_list".into(),
optional: false,
owned: false,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: None,
}
}
fn streaming_adapter(name: &str, request_type: &str) -> AdapterConfig {
AdapterConfig {
name: name.to_string(),
pattern: AdapterPattern::Streaming,
core_path: format!("sample::{name}"),
params: Vec::new(),
returns: None,
error_type: None,
owner_type: Some("SampleEngine".to_string()),
item_type: Some("sample::SampleEvent".to_string()),
gil_release: false,
trait_name: None,
trait_method: None,
detect_async: false,
request_type: Some(request_type.to_string()),
skip_languages: Vec::new(),
}
}
#[test]
fn a_single_streaming_call_passes_the_typed_request_not_the_flat_url() {
let fixture = Fixture {
id: "stream_a_document".into(),
description: "Stream a document".into(),
input: serde_json::json!({"engine": {}, "url": "https://example.com/doc"}),
preserve_input_urls: true,
..Fixture::default()
};
let mut call = CallConfig {
function: "stream_events".into(),
result_var: "events".into(),
r#async: true,
streaming: Some(StreamingConfig::Enabled(true)),
args: vec![engine_handle_arg(), url_arg()],
..CallConfig::default()
};
call.overrides.insert("csharp".into(), CallOverride::default());
let config = ResolvedCrateConfig {
name: "sample".into(),
adapters: vec![streaming_adapter("stream_events", "sample::SampleStreamRequest")],
..ResolvedCrateConfig::default()
};
let body = render_snippet_body(
&fixture,
&E2eConfig {
call,
..E2eConfig::default()
},
&config,
&[],
&[],
)
.expect("snippet renders");
assert_eq!(
line_containing(&body, "var url ="),
"var url = \"https://example.com/doc\";"
);
assert_eq!(
line_containing(&body, "urlReq ="),
"var urlReq = new SampleStreamRequest { Url = url };"
);
assert_eq!(
line_containing(&body, "StreamEventsAsync("),
"await foreach (var chunk in SampleConverter.StreamEventsAsync(engine, urlReq))"
);
assert!(
!body.contains("StreamEventsAsync(engine, url)"),
"must not pass the raw handle and URL positionally:\n{body}"
);
}
#[test]
fn a_batch_streaming_call_wraps_the_url_list_in_the_typed_batch_request() {
let fixture = Fixture {
id: "stream_several_documents".into(),
description: "Stream several documents".into(),
input: serde_json::json!({
"engine": {},
"urls": ["https://a.example.com/doc", "https://b.example.com/doc"]
}),
preserve_input_urls: true,
..Fixture::default()
};
let mut call = CallConfig {
function: "stream_events_batch".into(),
result_var: "events".into(),
r#async: true,
streaming: Some(StreamingConfig::Enabled(true)),
args: vec![engine_handle_arg(), urls_arg()],
..CallConfig::default()
};
call.overrides.insert("csharp".into(), CallOverride::default());
let config = ResolvedCrateConfig {
name: "sample".into(),
adapters: vec![streaming_adapter(
"stream_events_batch",
"sample::SampleBatchStreamRequest",
)],
..ResolvedCrateConfig::default()
};
let body = render_snippet_body(
&fixture,
&E2eConfig {
call,
..E2eConfig::default()
},
&config,
&[],
&[],
)
.expect("snippet renders");
assert_eq!(
line_containing(&body, "var urls ="),
"var urls = new System.Collections.Generic.List<string>(new string[] { \
\"https://a.example.com/doc\", \"https://b.example.com/doc\" });"
);
assert_eq!(
line_containing(&body, "urlsReq ="),
"var urlsReq = new SampleBatchStreamRequest { Urls = urls };"
);
assert_eq!(
line_containing(&body, "StreamEventsBatchAsync("),
"await foreach (var chunk in SampleConverter.StreamEventsBatchAsync(engine, urlsReq))"
);
}
#[test]
fn a_non_streaming_call_with_no_matching_adapter_keeps_the_flat_mock_url_shape() {
let fixture = Fixture {
id: "scrape_a_page".into(),
description: "Scrape a page".into(),
input: serde_json::json!({"url": "https://example.com/doc"}),
preserve_input_urls: true,
..Fixture::default()
};
let mut call = CallConfig {
function: "scrape".into(),
result_var: "result".into(),
args: vec![url_arg()],
..CallConfig::default()
};
call.overrides.insert("csharp".into(), CallOverride::default());
let config = ResolvedCrateConfig {
name: "sample".into(),
adapters: vec![streaming_adapter("stream_events", "sample::SampleStreamRequest")],
..ResolvedCrateConfig::default()
};
let body = render_snippet_body(
&fixture,
&E2eConfig {
call,
..E2eConfig::default()
},
&config,
&[],
&[],
)
.expect("snippet renders");
assert_eq!(
line_containing(&body, "SampleConverter.Scrape("),
"var result = SampleConverter.Scrape(url);"
);
assert!(
!body.contains("Req = new"),
"a call with no matching adapter must not wrap its mock_url arg in a request DTO:\n{body}"
);
}