use assert_cmd::Command;
use mockito::Server;
use predicates::prelude::PredicateBooleanExt;
fn olib() -> Command {
Command::cargo_bin("olib").unwrap()
}
fn olib_at(server: &Server) -> Command {
let mut cmd = olib();
cmd.args(["--base-url", &server.url()]);
cmd
}
fn olib_at_covers(main: &Server, covers: &Server) -> Command {
let mut cmd = olib();
cmd.args(["--base-url", &main.url(), "--covers-url", &covers.url()]);
cmd
}
fn json_body(body: &str) -> String {
body.to_string()
}
#[test]
fn help_exits_zero() {
olib().arg("--help").assert().success();
}
#[test]
fn help_contains_subcommand_list() {
let out = olib().arg("--help").output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
for cmd in ["work", "edition", "author", "search", "subject", "cover",
"list", "reading", "changes", "volume", "books", "query", "history"]
{
assert!(stdout.contains(cmd), "help missing subcommand '{cmd}'");
}
}
#[test]
fn version_exits_zero() {
olib().arg("--version").assert().success();
}
#[test]
fn version_contains_semver() {
let out = olib().arg("--version").output().unwrap();
let text = String::from_utf8_lossy(&out.stdout);
assert!(text.contains("0.1.0"), "version output: {text}");
}
#[test]
fn work_help_exits_zero() {
olib().args(["work", "--help"]).assert().success();
}
#[test]
fn search_books_help_exits_zero() {
olib().args(["search", "books", "--help"]).assert().success();
}
#[test]
fn work_get_rejects_bad_olid() {
olib()
.args(["work", "get", "NOTANOLID"])
.assert()
.failure()
.stderr(predicates::str::contains("InvalidInput").or(predicates::str::contains("error")));
}
#[test]
fn work_editions_rejects_bad_olid() {
olib()
.args(["work", "editions", "bad"])
.assert()
.failure();
}
#[test]
fn work_ratings_rejects_bad_olid() {
olib().args(["work", "ratings", "bad"]).assert().failure();
}
#[test]
fn work_bookshelves_rejects_bad_olid() {
olib().args(["work", "bookshelves", "bad"]).assert().failure();
}
#[test]
fn edition_get_rejects_bad_olid() {
olib().args(["edition", "get", "OL123W"]).assert().failure();
}
#[test]
fn edition_isbn_rejects_bad_isbn() {
olib().args(["edition", "isbn", "0000000000"]).assert().failure();
}
#[test]
fn edition_isbn_rejects_too_short() {
olib().args(["edition", "isbn", "123"]).assert().failure();
}
#[test]
fn author_get_rejects_bad_olid() {
olib().args(["author", "get", "OL123W"]).assert().failure();
}
#[test]
fn cover_url_rejects_unknown_key_type() {
olib()
.args(["cover", "url", "badtype", "12345", "large"])
.assert()
.failure()
.stderr(predicates::str::contains("unknown cover key type"));
}
#[test]
fn cover_url_rejects_unknown_size() {
olib()
.args(["cover", "url", "id", "12345", "jumbo"])
.assert()
.failure()
.stderr(predicates::str::contains("unknown image size"));
}
#[test]
fn cover_photo_rejects_bad_olid() {
olib()
.args(["cover", "photo", "NOT_AN_OLID", "large"])
.assert()
.failure();
}
#[test]
fn changes_rejects_unknown_kind() {
let mut server = Server::new();
olib_at(&server)
.args(["changes", "--kind", "not-a-real-kind"])
.assert()
.failure()
.stderr(predicates::str::contains("unknown change kind"));
let _ = server; }
#[test]
fn books_rejects_unknown_bibkey_prefix() {
let mut server = Server::new();
olib_at(&server)
.args(["books", "ASIN:B000FC1234"])
.assert()
.failure();
let _ = server;
}
#[test]
fn list_user_rejects_empty_username() {
let mut server = Server::new();
olib_at(&server)
.args(["list", "user", "alice!@#"])
.assert()
.failure();
let _ = server;
}
#[test]
fn work_get_prints_title() {
let mut server = Server::new();
let mock = server
.mock("GET", "/works/OL45804W.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"key":"/works/OL45804W","title":"Fantastic Mr. Fox","subjects":["Animals","Fiction"]}"#)
.create();
let out = olib_at(&server)
.args(["work", "get", "OL45804W"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Fantastic Mr. Fox"), "stdout: {text}");
mock.assert();
}
#[test]
fn work_get_json_flag_emits_valid_json() {
let mut server = Server::new();
let body = r#"{"key":"/works/OL45804W","title":"Fantastic Mr. Fox"}"#;
let _mock = server
.mock("GET", "/works/OL45804W.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(body)
.create();
let out = olib_at(&server)
.args(["--json", "work", "get", "OL45804W"])
.assert()
.success()
.get_output()
.stdout
.clone();
let parsed: serde_json::Value = serde_json::from_slice(&out)
.expect("--json output should be valid JSON");
assert_eq!(parsed["title"], "Fantastic Mr. Fox");
}
#[test]
fn work_get_404_exits_nonzero() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/works/OL99999W.json")
.with_status(404)
.create();
olib_at(&server)
.args(["work", "get", "OL99999W"])
.assert()
.failure()
.stderr(predicates::str::contains("error"));
}
#[test]
fn work_editions_prints_count() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/works/OL45804W/editions.json")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("limit".into(), "5".into()),
mockito::Matcher::UrlEncoded("offset".into(), "0".into()),
]))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"entries":[{"key":"/books/OL1M","title":"Edition One","publish_date":"1990"}],"size":1}"#)
.create();
let out = olib_at(&server)
.args(["work", "editions", "OL45804W", "--limit", "5"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Edition One"), "stdout: {text}");
}
#[test]
fn work_ratings_prints_average() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/works/OL45804W/ratings.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"summary":{"average":4.25,"count":1000},"counts":{"1":10,"2":20,"3":100,"4":350,"5":520}}"#)
.create();
let out = olib_at(&server)
.args(["work", "ratings", "OL45804W"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("4.25"), "stdout: {text}");
}
#[test]
fn work_bookshelves_prints_counts() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/works/OL45804W/bookshelves.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"counts":{"want_to_read":500,"currently_reading":100,"already_read":800}}"#)
.create();
let out = olib_at(&server)
.args(["work", "bookshelves", "OL45804W"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("500"), "stdout: {text}");
assert!(text.contains("800"), "stdout: {text}");
}
#[test]
fn edition_get_prints_title() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/books/OL7353617M.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"key":"/books/OL7353617M","title":"Fantastic Mr. Fox","publishers":["Puffin"],"publish_date":"1988"}"#)
.create();
let out = olib_at(&server)
.args(["edition", "get", "OL7353617M"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Fantastic Mr. Fox"), "stdout: {text}");
}
#[test]
fn edition_isbn_prints_title() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/isbn/9780306406157.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"key":"/books/OL1M","title":"How to Measure Anything","publish_date":"2007"}"#)
.create();
let out = olib_at(&server)
.args(["edition", "isbn", "9780306406157"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("How to Measure Anything"), "stdout: {text}");
}
#[test]
fn edition_isbn_json_flag_emits_valid_json() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/isbn/9780306406157.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"key":"/books/OL1M","title":"How to Measure Anything"}"#)
.create();
let out = olib_at(&server)
.args(["--json", "edition", "isbn", "9780306406157"])
.assert()
.success()
.get_output()
.stdout
.clone();
let parsed: serde_json::Value =
serde_json::from_slice(&out).expect("should be valid JSON");
assert_eq!(parsed["title"], "How to Measure Anything");
}
#[test]
fn author_get_prints_name() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/authors/OL23919A.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"key":"/authors/OL23919A","name":"J. K. Rowling","birth_date":"31 July 1965"}"#)
.create();
let out = olib_at(&server)
.args(["author", "get", "OL23919A"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("J. K. Rowling"), "stdout: {text}");
}
#[test]
fn author_works_prints_entry() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/authors/OL23919A/works.json")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("limit".into(), "5".into()),
mockito::Matcher::UrlEncoded("offset".into(), "0".into()),
]))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"entries":[{"key":"/works/OL82563W","title":"Harry Potter and the Philosopher's Stone"}]}"#)
.create();
let out = olib_at(&server)
.args(["author", "works", "OL23919A", "--limit", "5"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Harry Potter"), "stdout: {text}");
}
#[test]
fn search_books_prints_num_found() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/search.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"num_found":42,"start":0,"docs":[{"key":"/works/OL1W","title":"Rust Programming","edition_count":3}]}"#)
.create();
let out = olib_at(&server)
.args(["search", "books", "--q", "rust"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("42"), "stdout: {text}");
assert!(text.contains("Rust Programming"), "stdout: {text}");
}
#[test]
fn search_books_json_output_is_parseable() {
let mut server = Server::new();
let body = r#"{"num_found":1,"start":0,"docs":[{"key":"/works/OL1W","title":"Test Book","edition_count":1}]}"#;
let _mock = server
.mock("GET", "/search.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(body)
.create();
let out = olib_at(&server)
.args(["--json", "search", "books", "--q", "test"])
.assert()
.success()
.get_output()
.stdout
.clone();
let parsed: serde_json::Value =
serde_json::from_slice(&out).expect("should be valid JSON");
assert_eq!(parsed["num_found"], 1);
}
#[test]
fn search_authors_prints_name() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/search/authors.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"num_found":1,"start":0,"docs":[{"key":"/authors/OL1A","name":"J. R. R. Tolkien","work_count":20}]}"#)
.create();
let out = olib_at(&server)
.args(["search", "authors", "--q", "tolkien"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Tolkien"), "stdout: {text}");
}
#[test]
fn search_subjects_prints_result() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/search/subjects.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"num_found":2,"start":0,"docs":[{"key":"/subjects/fantasy","name":"Fantasy","work_count":5000},{"key":"/subjects/fantasy_fiction","name":"Fantasy fiction","work_count":3000}]}"#)
.create();
let out = olib_at(&server)
.args(["search", "subjects", "fantasy"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Fantasy"), "stdout: {text}");
}
#[test]
fn search_lists_prints_result() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/search/lists.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"num_found":1,"start":0,"docs":[{"key":"/people/alice/lists/OL1L","name":"My Favourites"}]}"#)
.create();
let out = olib_at(&server)
.args(["search", "lists", "tolkien"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("My Favourites"), "stdout: {text}");
}
#[test]
fn search_inside_prints_result() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/search/inside.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"num_found":1,"start":0,"docs":[{"ia":"moby_dick","title":"Moby Dick","author":"Melville","text":"Call me Ishmael."}]}"#)
.create();
let out = olib_at(&server)
.args(["search", "inside", "call me ishmael"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Moby Dick"), "stdout: {text}");
}
#[test]
fn subject_prints_work_count() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/subjects/love.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"key":"/subjects/love","name":"Love","work_count":5000,"works":[{"key":"/works/OL1W","title":"Romeo and Juliet"}]}"#)
.create();
let out = olib_at(&server)
.args(["subject", "love"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("5000"), "stdout: {text}");
assert!(text.contains("Romeo and Juliet"), "stdout: {text}");
}
#[test]
fn subject_rejects_uppercase_slug() {
let mut server = Server::new();
olib_at(&server)
.args(["subject", "Love"])
.assert()
.failure();
let _ = server;
}
#[test]
fn cover_url_id_prints_url() {
let out = olib()
.args(["cover", "url", "id", "5428012", "large"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("5428012"), "stdout: {text}");
assert!(text.contains("-L.jpg"), "stdout: {text}");
}
#[test]
fn cover_url_isbn_prints_url() {
let out = olib()
.args(["cover", "url", "isbn", "9780451450524", "medium"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("9780451450524"), "stdout: {text}");
assert!(text.contains("-M.jpg"), "stdout: {text}");
}
#[test]
fn cover_url_size_alias_s_works() {
let out = olib()
.args(["cover", "url", "id", "123", "s"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("-S.jpg"), "stdout: {text}");
}
#[test]
fn cover_photo_prints_url() {
let out = olib()
.args(["cover", "photo", "OL23919A", "large"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("OL23919A"), "stdout: {text}");
}
#[test]
fn cover_meta_prints_fields() {
let mut main_server = Server::new();
let mut covers_server = Server::new();
let _mock = covers_server
.mock("GET", "/b/id/5428012.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"id:5428012":[{"id":5428012,"width":400,"height":600,"url":"https://covers.openlibrary.org/b/id/5428012-L.jpg"}]}"#)
.create();
let out = olib_at_covers(&main_server, &covers_server)
.args(["cover", "meta", "id", "5428012"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("5428012"), "stdout: {text}");
let _ = main_server;
}
#[test]
fn list_user_prints_list_name() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/people/alice/lists.json")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("limit".into(), "20".into()),
mockito::Matcher::UrlEncoded("offset".into(), "0".into()),
]))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"lists":[{"key":"/people/alice/lists/OL1L","name":"My Favourites","seed_count":5}],"size":1}"#)
.create();
let out = olib_at(&server)
.args(["list", "user", "alice"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("My Favourites"), "stdout: {text}");
}
#[test]
fn list_show_prints_name_and_counts() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/people/alice/lists/OL1L.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"key":"/people/alice/lists/OL1L","name":"My Favourites","seed_count":7,"edition_count":12}"#)
.create();
let out = olib_at(&server)
.args(["list", "show", "alice", "OL1L"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("My Favourites"), "stdout: {text}");
}
#[test]
fn list_subjects_prints_subjects() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/people/alice/lists/OL1L/subjects.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"subjects":[{"name":"Fantasy","count":3}],"places":[],"people":[],"times":[]}"#)
.create();
let out = olib_at(&server)
.args(["list", "subjects", "alice", "OL1L"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Fantasy"), "stdout: {text}");
}
#[test]
fn list_seeds_prints_entries() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/people/alice/lists/OL1L/seeds.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"entries":[{"key":"/works/OL45804W"},{"url":"/subjects/love","title":"Love"}],"size":2}"#)
.create();
let out = olib_at(&server)
.args(["list", "seeds", "alice", "OL1L"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("/works/OL45804W"), "stdout: {text}");
assert!(text.contains("Love"), "stdout: {text}");
}
#[test]
fn reading_already_read_prints_title() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/people/alice/books/already-read.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"reading_log_entries":[{"work":{"key":"/works/OL1W","title":"Dune","author_names":["Frank Herbert"]},"logged_date":"2024-01-15"}]}"#)
.create();
let out = olib_at(&server)
.args(["reading", "already-read", "alice"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Dune"), "stdout: {text}");
assert!(text.contains("Frank Herbert"), "stdout: {text}");
}
#[test]
fn reading_want_to_read_prints_entries() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/people/bob/books/want-to-read.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"reading_log_entries":[{"work":{"key":"/works/OL2W","title":"Foundation","author_names":["Isaac Asimov"]}}]}"#)
.create();
let out = olib_at(&server)
.args(["reading", "want-to-read", "bob"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("Foundation"), "stdout: {text}");
}
#[test]
fn changes_recent_prints_entries() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/recentchanges.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"[{"kind":"edit-book","key":"/books/OL1M","timestamp":"2024-06-15T10:00:00","comment":"fixed typo"}]"#)
.create();
let out = olib_at(&server)
.args(["changes"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("edit-book"), "stdout: {text}");
assert!(text.contains("fixed typo"), "stdout: {text}");
}
#[test]
fn changes_by_date_and_kind_calls_correct_path() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/recentchanges/2024/06/15/add-cover.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"[{"kind":"add-cover","key":"/books/OL1M","timestamp":"2024-06-15T08:00:00"}]"#)
.create();
let out = olib_at(&server)
.args(["changes", "--date", "2024-06-15", "--kind", "add-cover"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("add-cover"), "stdout: {text}");
}
#[test]
fn changes_json_output_is_array() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/recentchanges.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"[{"kind":"edit-book","key":"/books/OL1M"}]"#)
.create();
let out = olib_at(&server)
.args(["--json", "changes"])
.assert()
.success()
.get_output()
.stdout
.clone();
let parsed: serde_json::Value =
serde_json::from_slice(&out).expect("should be valid JSON");
assert!(parsed.is_array(), "expected array");
}
#[test]
fn volume_isbn_prints_record() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/api/volumes/brief/isbn/0451450523.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"records":{"/books/OL1M":{"title":"1984","url":"https://openlibrary.org/books/OL1M"}},"items":[]}"#)
.create();
let out = olib_at(&server)
.args(["volume", "isbn", "0451450523"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("1984"), "stdout: {text}");
}
#[test]
fn volume_rejects_empty_value() {
let mut server = Server::new();
olib_at(&server)
.args(["volume", "isbn", ""])
.assert()
.failure();
let _ = server;
}
#[test]
fn books_bibkey_prints_url() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/api/books")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"ISBN:0451450523":{"bib_key":"ISBN:0451450523","info_url":"https://openlibrary.org/books/OL1M","preview":"noview"}}"#)
.create();
let out = olib_at(&server)
.args(["books", "ISBN:0451450523"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("ISBN:0451450523"), "stdout: {text}");
}
#[test]
fn query_type_edition_prints_result() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/query.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"result":["/books/OL1M","/books/OL2M"]}"#)
.create();
let out = olib_at(&server)
.args(["query", "--type", "/type/edition", "--field", "isbn=0451450523"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("/books/OL1M"), "stdout: {text}");
}
#[test]
fn history_prints_revisions() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/works/OL45804W.json")
.match_query(mockito::Matcher::UrlEncoded("m".into(), "history".into()))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"[{"revision":3,"timestamp":"2024-06-01T12:00:00","comment":"added subject"}]"#)
.create();
let out = olib_at(&server)
.args(["history", "/works/OL45804W"])
.assert()
.success()
.get_output()
.stdout
.clone();
let text = String::from_utf8_lossy(&out);
assert!(text.contains("added subject"), "stdout: {text}");
assert!(text.contains("r3"), "stdout: {text}");
}
#[test]
fn history_json_output_is_array() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/works/OL45804W.json")
.match_query(mockito::Matcher::UrlEncoded("m".into(), "history".into()))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"[{"revision":1,"timestamp":"2023-01-01T00:00:00"}]"#)
.create();
let out = olib_at(&server)
.args(["--json", "history", "/works/OL45804W"])
.assert()
.success()
.get_output()
.stdout
.clone();
let parsed: serde_json::Value =
serde_json::from_slice(&out).expect("should be valid JSON");
assert!(parsed.is_array());
assert_eq!(parsed[0]["revision"], 1);
}
#[test]
fn json_flag_author_get_valid_json() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/authors/OL23919A.json")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"key":"/authors/OL23919A","name":"J. K. Rowling"}"#)
.create();
let out = olib_at(&server)
.args(["--json", "author", "get", "OL23919A"])
.assert()
.success()
.get_output()
.stdout
.clone();
let parsed: serde_json::Value =
serde_json::from_slice(&out).expect("should be valid JSON");
assert_eq!(parsed["name"], "J. K. Rowling");
}
#[test]
fn json_flag_subject_valid_json() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/subjects/love.json")
.match_query(mockito::Matcher::Any)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"key":"/subjects/love","name":"Love","work_count":1000,"works":[]}"#)
.create();
let out = olib_at(&server)
.args(["--json", "subject", "love"])
.assert()
.success()
.get_output()
.stdout
.clone();
let parsed: serde_json::Value =
serde_json::from_slice(&out).expect("should be valid JSON");
assert_eq!(parsed["name"], "Love");
assert_eq!(parsed["work_count"], 1000);
}