| 1 | //! Actual complete HTML bytes stay private through the real Builtin selection. |
| 2 | use super::*; |
| 3 | use crate::extension_host::tests::node_for_tests; |
| 4 | use crate::extension_host::{ExtensionHostManager, ExtensionHostOptions, TestManagerGuard}; |
| 5 | use crate::features::{Feature, Features}; |
| 6 | use crate::plugins::activation::TestPolicyGuard; |
| 7 | use crate::tools::spec::ToolContext; |
| 8 | use std::sync::Arc; |
| 9 | #[tokio::test(flavor = "current_thread")] |
| 10 | async fn real_host_complete_html_region_selection_preserves_all_document_bytes() { |
| 11 | let _home = crate::test_support::SealedHome::new(); |
| 12 | let _policy = TestPolicyGuard::extension_host(false); |
| 13 | let Some(node) = node_for_tests("real_host_web_html_parity") else { |
| 14 | return; |
| 15 | }; |
| 16 | let root = tempfile::tempdir().unwrap(); |
| 17 | let manager = Arc::new(ExtensionHostManager::new(ExtensionHostOptions { |
| 18 | runtime: crate::config::ExtensionHostRuntime::Node, |
| 19 | node_override: Some(node), |
| 20 | root: Some(root.path().join("host")), |
| 21 | ..ExtensionHostOptions::default() |
| 22 | })); |
| 23 | let _manager = TestManagerGuard::install(Arc::clone(&manager)); |
| 24 | let rust = ToolContext::new(root.path()); |
| 25 | let mut flags = Features::with_defaults(); |
| 26 | flags.enable(Feature::WebExtractHost); |
| 27 | let host = rust.clone().with_features(flags); |
| 28 | let large = format!( |
| 29 | "<html><head><title>Whole document</title></head><body><nav>private navigation</nav><main><a href='../relative'>link</a><p>{}END_SENTINEL</p></main></body></html>", |
| 30 | "Meaningful 漢字 words remain complete. ".repeat(40_000) |
| 31 | ); |
| 32 | for html in ["<body><article>short</article><main>Five meaningful words survive this complete main-region extraction path.</main></body>".to_owned(),"<body><article>Five meaningful words stay in first article region.</article><main>Other valid words live in the second main region.</main></body>".into(),large] { |
| 33 | let expected=extract_document("https://example.com/docs/page",Some("text/html"),html.as_bytes(),Some(&rust)).await.unwrap(); |
| 34 | let actual=extract_document("https://example.com/docs/page",Some("text/html"),html.as_bytes(),Some(&host)).await.unwrap(); |
| 35 | assert_eq!(actual.text,expected.text);assert_eq!(actual.markdown,expected.markdown);assert_eq!(actual.cleaned_html,expected.cleaned_html);assert_eq!(actual.title,expected.title); |
| 36 | if html.contains("END_SENTINEL"){assert!(actual.text.contains("END_SENTINEL"));assert!(actual.markdown.len()>1024*1024);} |
| 37 | } |
| 38 | let error = extract_document( |
| 39 | "https://example.com/shell", |
| 40 | Some("text/html"), |
| 41 | b"<body><div id='root'></div></body>", |
| 42 | Some(&host), |
| 43 | ) |
| 44 | .await |
| 45 | .unwrap_err(); |
| 46 | assert!(error.content()); |
| 47 | assert!(is_js_shell_error(&error.error)); |
| 48 | manager.shutdown().await; |
| 49 | } |
| 50 | #[tokio::test(flavor = "current_thread")] |
| 51 | async fn host_failure_with_shell_marker_never_earns_revalidation() { |
| 52 | use super::super::fetch::{FetchOptions, fetch_readable_with_initial_pin}; |
| 53 | use wiremock::matchers::method; |
| 54 | use wiremock::{Mock, MockServer, ResponseTemplate}; |
| 55 | for origin in [ |
| 56 | super::super::adapter::FailureOrigin::Host, |
| 57 | super::super::adapter::FailureOrigin::CaptureGuard, |
| 58 | ] { |
| 59 | let server = MockServer::start().await; |
| 60 | Mock::given(method("GET")) |
| 61 | .respond_with( |
| 62 | ResponseTemplate::new(200) |
| 63 | .insert_header("content-type", "text/html") |
| 64 | .set_body_string("<body>fixture</body>"), |
| 65 | ) |
| 66 | .mount(&server) |
| 67 | .await; |
| 68 | let context = ToolContext::new(".").with_state_namespace(format!("web-origin-{origin:?}")); |
| 69 | let url = format!("http://public.example:{}/origin", server.address().port()); |
| 70 | let result: Result<super::super::fetch::ReadableFetch<()>, ToolError> = |
| 71 | fetch_readable_with_initial_pin( |
| 72 | &url, |
| 73 | &FetchOptions::new(std::time::Duration::from_secs(5), 1024, "text/html"), |
| 74 | &context, |
| 75 | "web-origin", |
| 76 | Some(( |
| 77 | "public.example".into(), |
| 78 | std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), |
| 79 | )), |
| 80 | move |_| { |
| 81 | Box::pin(async move { |
| 82 | Err(AdapterFailure { |
| 83 | origin, |
| 84 | error: ToolError::execution_failed(JS_SHELL_MARKER), |
| 85 | }) |
| 86 | }) |
| 87 | }, |
| 88 | ) |
| 89 | .await; |
| 90 | assert!(result.unwrap_err().to_string().contains(JS_SHELL_MARKER)); |
| 91 | assert_eq!(server.received_requests().await.unwrap().len(), 1); |
| 92 | } |
| 93 | } |
| 94 |