duckduckgo-core 0.1.6

DuckDuckGo search client library for duckduckgo-cli
Documentation
use crate::Client;
use crate::parser::ParsedPage;
use crate::search::SearchResult;

use super::state::FetchState;

fn result(title: &str) -> SearchResult {
    SearchResult {
        position: 0,
        title: title.to_owned(),
        url: format!("https://example.com/{title}"),
        snippet: String::new(),
    }
}

#[test]
fn slice_results_renumbers_logical_page_positions() {
    let mut state = FetchState::new(4);
    let builder = Client::builder().num(2).build().unwrap().search("rust");
    state
        .accept_page(
            ParsedPage {
                instant_answer: None,
                results: vec![result("one"), result("two"), result("three")],
                next_fields: None,
                no_results: false,
            },
            &builder,
        )
        .unwrap();
    let page_two = state.slice_results(2, 2);
    assert_eq!(page_two.len(), 1);
    assert_eq!(page_two[0].position, 3);
    assert_eq!(page_two[0].title, "three");
}

#[test]
fn missing_next_fields_for_later_page_is_parse_error() {
    let builder = Client::builder()
        .num(2)
        .build()
        .unwrap()
        .search("rust")
        .page(2);
    let mut state = FetchState::new(4);
    let err = state
        .accept_page(
            ParsedPage {
                instant_answer: None,
                results: vec![result("one")],
                next_fields: None,
                no_results: false,
            },
            &builder,
        )
        .unwrap_err();
    assert!(err.to_string().contains("missing next-page fields"));
}

#[test]
fn first_instant_answer_wins_across_pages() {
    let builder = Client::builder().build().unwrap().search("rust");
    let mut state = FetchState::new(2);
    for answer in ["first", "second"] {
        state
            .accept_page(
                ParsedPage {
                    instant_answer: Some(answer.to_owned()),
                    results: vec![result(answer)],
                    next_fields: Some(vec![("s".to_owned(), "30".to_owned())]),
                    no_results: false,
                },
                &builder,
            )
            .unwrap();
    }
    assert_eq!(state.instant_answer.as_deref(), Some("first"));
}