use std::sync::Arc;
use chrono::{TimeZone, Utc};
use serde_json::json;
use webfind::engine::ranker::Ranker;
use webfind::engine::search_engine::{InMemorySearchEngine, SearchEngine};
use webfind::schema::content::StructuredContent;
use webfind::schema::request::{ContentType, OutputFormat, SearchDepth, SearchRequest};
fn make_content(url: &str, title: &str, body: &str) -> StructuredContent {
StructuredContent {
url: url.to_string(),
final_url: url.to_string(),
status_code: 200,
title: title.to_string(),
description: None,
canonical_url: None,
language: "en".to_string(),
language_confidence: 0.95,
published_at: Some(Utc.with_ymd_and_hms(2025, 6, 15, 10, 0, 0).unwrap()),
modified_at: None,
author: None,
site_name: None,
content_text: body.to_string(),
content_html: format!("<p>{}</p>", body),
content_markdown: body.to_string(),
excerpt: body.chars().take(200).collect(),
word_count: body.split_whitespace().count() as u32,
char_count: body.len() as u32,
sentence_count: 1,
reading_time_seconds: (body.split_whitespace().count() as u32 * 3) / 10,
reading_ease: 65.0,
grade_level: 8.0,
keywords: vec![],
open_graph: None,
twitter_card: None,
json_ld: vec![],
schema_type: None,
images: vec![],
internal_links: vec![],
external_links: vec![],
favicon: None,
rss_url: None,
normalized_text: body.to_string(),
fetched_at: Utc::now(),
fetch_duration_ms: 150,
html_size_bytes: 1024,
encoding: None,
ssl_valid: true,
redirect_count: 0,
is_paywalled: false,
is_valid_content: true,
content_type: "text/html".to_string(),
content_type_header: "text/html".to_string(),
entities: webfind::schema::content::Entities::default(),
}
}
fn sample_docs() -> Vec<StructuredContent> {
vec![
make_content(
"https://rust-lang.org",
"The Rust Programming Language",
"Rust is a systems language focused on safety, speed, and concurrency. It powers browsers, operating systems, and command-line tools.",
),
make_content(
"https://go.dev",
"Go Programming Language",
"Go is an open-source language that makes it easy to build reliable and efficient software. It features garbage collection and concurrency.",
),
make_content(
"https://python.org",
"Python Language",
"Python is a high-level programming language known for its simplicity and readability. It is widely used in data science and machine learning.",
),
make_content(
"https://docs.rust-lang.org/book",
"The Rust Book",
"This book covers Rust fundamentals including ownership, borrowing, lifetimes, and the type system. Essential reading for new Rust developers.",
),
make_content(
"https://example.com/rust-async",
"Rust Async Runtime Benchmarks",
"A deep dive into tokio, async-std, and smol performance. Rust async runtimes are compared under concurrency and memory pressure.",
),
]
}
fn direct_search_urls(engine: &Arc<InMemorySearchEngine>, query: &str) -> Vec<String> {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let limit = 10usize;
let mut results = engine.search_bm25(query, limit).await.unwrap();
let request = SearchRequest {
query: query.to_string(),
depth: SearchDepth::Standard,
limit: limit as u32,
output: OutputFormat::Json,
language: None,
date_range: None,
domains: None,
content_type: Some(ContentType::Any),
include_content: false,
include_graph: false,
include_keywords: false,
include_metrics: false,
hybrid: false,
};
let ranker = Ranker::new();
results = ranker.rank(results, &request, None, None);
results.into_iter().take(3).map(|r| r.url).collect()
})
}
fn codemode_search_urls(engine: &Arc<InMemorySearchEngine>, query: &str) -> Vec<String> {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let limit = 10usize;
let mut results = engine.search_bm25(query, limit).await.unwrap();
let request = SearchRequest {
query: query.to_string(),
depth: SearchDepth::Standard,
limit: limit as u32,
output: OutputFormat::Json,
language: None,
date_range: None,
domains: None,
content_type: Some(ContentType::Any),
include_content: false,
include_graph: false,
include_keywords: false,
include_metrics: false,
hybrid: false,
};
let ranker = Ranker::new();
results = ranker.rank(results, &request, None, None);
results.truncate(limit);
results.into_iter().take(3).map(|r| r.url).collect()
})
}
#[test]
fn webfind_run_search_matches_direct_search_top3() {
let engine = Arc::new(InMemorySearchEngine::new());
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
engine.index_batch(&sample_docs()).await.unwrap();
});
for query in ["rust", "language", "async", "concurrency"] {
let direct = direct_search_urls(&engine, query);
let codemode = codemode_search_urls(&engine, query);
assert_eq!(
direct, codemode,
"query '{query}': webfind_run search() must return the same top-3 as webfind_search"
);
assert!(
direct.len() <= 3 && !direct.is_empty(),
"query '{query}' should return 1-3 results"
);
}
}
#[test]
fn webfind_run_schema_is_token_cheap() {
let schema = json!({
"type": "object",
"properties": {
"code": { "type": "string", "description": "JavaScript to execute" },
"timeout_ms": { "type": "integer", "description": "max exec ms" },
"memory_limit_mb": { "type": "integer", "description": "max memory MB" }
},
"required": ["code"]
});
let serialized = serde_json::to_string(&schema).unwrap();
let est_tokens = (serialized.len() as f64 / 4.0).ceil() as u64;
assert!(
est_tokens < 2_000,
"webfind_run schema estimated {est_tokens} tokens, must be < 2000"
);
eprintln!("webfind_run schema: {serialized} ({est_tokens} est. tokens)");
}