#![allow(clippy::expect_used, clippy::unwrap_used)]
use predicates::prelude::*;
use wiremock::matchers::{body_json, method, path, query_param, query_param_is_missing};
use wiremock::{Mock, ResponseTemplate};
mod harness;
use harness::{Harness, fixture};
const TOKEN: &str = "0b6c2a4e-1f3d-4e5a-9b7c-8d9e0f1a2b3c";
async fn page_is_4521(harness: &Harness) {
Mock::given(method("GET"))
.and(path("/v1/pages"))
.and(query_param("slug", "users/ilubenets/runbook"))
.respond_with(ResponseTemplate::new(200).set_body_json(fixture("wiki_page.json")))
.mount(&harness.server)
.await;
}
async fn nothing_was_sent(harness: &Harness) {
let requests = harness.server.received_requests().await.expect("recorded");
assert!(requests.is_empty(), "{requests:?}");
}
#[tokio::test]
async fn a_page_is_created_from_a_file_after_the_write_is_announced() {
let harness = Harness::new().await;
let dir = tempfile::tempdir().expect("temp dir");
let file = dir.path().join("notes.md");
std::fs::write(&file, "# Notes\n\n- one\n").unwrap();
Mock::given(method("POST"))
.and(path("/v1/pages"))
.and(query_param_is_missing("is_silent"))
.and(body_json(serde_json::json!({
"slug": "users/ilubenets/notes",
"title": "Notes",
"content": "# Notes\n\n- one\n"
})))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"id": 4600, "slug": "users/ilubenets/notes", "title": "Notes", "page_type": "wysiwyg"
})))
.mount(&harness.server)
.await;
harness
.run(&[
"wiki",
"create",
"users/ilubenets/notes",
"--title",
"Notes",
"--from",
file.to_str().unwrap(),
])
.assert()
.success()
.stdout("created users/ilubenets/notes (id 4600)\n")
.stderr(predicate::str::contains("profile="));
}
#[tokio::test]
async fn a_dry_run_sends_nothing() {
let harness = Harness::new().await;
harness
.run(&[
"wiki",
"create",
"users/ilubenets/notes",
"--title",
"Notes",
"--dry-run",
])
.assert()
.success()
.stderr(predicate::str::contains(
"dry run: would create wiki page `users/ilubenets/notes`",
));
nothing_was_sent(&harness).await;
harness
.run(&["wiki", "delete", "users/ilubenets/runbook", "--dry-run"])
.assert()
.success();
nothing_was_sent(&harness).await;
}
#[tokio::test]
async fn an_update_from_stdin_is_sent_by_page_id() {
let harness = Harness::new().await;
page_is_4521(&harness).await;
Mock::given(method("POST"))
.and(path("/v1/pages/4521"))
.and(query_param("allow_merge", "true"))
.and(query_param("is_silent", "true"))
.and(body_json(serde_json::json!({ "content": "new text\n" })))
.respond_with(ResponseTemplate::new(200).set_body_json(fixture("wiki_page.json")))
.mount(&harness.server)
.await;
harness
.run(&[
"wiki",
"update",
"users/ilubenets/runbook",
"--from",
"-",
"--merge",
"--silent",
])
.write_stdin("new text\n")
.assert()
.success()
.stdout("updated users/ilubenets/runbook (id 4521)\n");
}
#[tokio::test]
async fn an_update_with_nothing_to_change_is_refused() {
let harness = Harness::new().await;
harness
.run(&["wiki", "update", "users/ilubenets/runbook"])
.assert()
.code(2)
.stderr(predicate::str::contains("nothing to change"));
nothing_was_sent(&harness).await;
}
#[tokio::test]
async fn append_goes_to_the_top_or_an_anchor() {
let harness = Harness::new().await;
page_is_4521(&harness).await;
Mock::given(method("POST"))
.and(path("/v1/pages/4521/append-content"))
.and(body_json(serde_json::json!({
"content": "entry\n", "body": { "location": "top" }
})))
.respond_with(ResponseTemplate::new(200).set_body_json(fixture("wiki_page.json")))
.mount(&harness.server)
.await;
Mock::given(method("POST"))
.and(path("/v1/pages/4521/append-content"))
.and(body_json(serde_json::json!({
"content": "entry\n", "anchor": { "name": "#deploy" }
})))
.respond_with(ResponseTemplate::new(200).set_body_json(fixture("wiki_page.json")))
.mount(&harness.server)
.await;
for place in [&["--top"][..], &["--anchor", "#deploy"]] {
let mut args = vec!["wiki", "append", "users/ilubenets/runbook", "--from", "-"];
args.extend_from_slice(place);
harness
.run(&args)
.write_stdin("entry\n")
.assert()
.success()
.stdout("appended to users/ilubenets/runbook (id 4521)\n");
}
}
#[tokio::test]
async fn a_delete_prints_the_token_and_the_restore_command() {
let harness = Harness::new().await;
page_is_4521(&harness).await;
Mock::given(method("DELETE"))
.and(path("/v1/pages/4521"))
.and(query_param_is_missing("recursive"))
.respond_with(
ResponseTemplate::new(200)
.set_body_json(serde_json::json!({ "recovery_token": TOKEN })),
)
.mount(&harness.server)
.await;
harness
.run(&["wiki", "delete", "users/ilubenets/runbook"])
.assert()
.success()
.stdout(predicate::str::contains(format!("recovery token {TOKEN}")))
.stdout(predicate::str::contains(format!(
"ytcli wiki restore {TOKEN}"
)));
}
#[tokio::test]
async fn a_recursive_delete_needs_yes() {
let harness = Harness::new().await;
harness
.run(&["wiki", "delete", "users/ilubenets/runbook", "--recursive"])
.assert()
.code(2)
.stderr(predicate::str::contains("--yes"));
nothing_was_sent(&harness).await;
}
#[tokio::test]
async fn a_page_is_restored_by_its_token() {
let harness = Harness::new().await;
Mock::given(method("POST"))
.and(path(format!("/v1/recovery_tokens/{TOKEN}/recover")))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"id": 4521, "slug": "users/ilubenets/runbook", "pages_count": 3
})))
.mount(&harness.server)
.await;
harness
.run(&["wiki", "restore", TOKEN])
.assert()
.success()
.stdout("restored users/ilubenets/runbook (id 4521, 3 pages)\n");
}
#[tokio::test]
async fn a_refused_write_names_wiki_write() {
let harness = Harness::new().await;
Mock::given(method("POST"))
.and(path("/v1/pages"))
.respond_with(ResponseTemplate::new(403))
.mount(&harness.server)
.await;
harness
.run(&[
"wiki",
"create",
"users/ilubenets/notes",
"--title",
"Notes",
])
.assert()
.code(3)
.stderr(predicate::str::contains("wiki:write"));
}
#[tokio::test]
async fn a_write_answers_json_when_asked() {
let harness = Harness::new().await;
Mock::given(method("POST"))
.and(path("/v1/pages"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"id": 4600, "slug": "users/ilubenets/notes", "title": "Notes", "page_type": "wysiwyg"
})))
.mount(&harness.server)
.await;
let output = harness
.run(&[
"wiki",
"create",
"users/ilubenets/notes",
"--title",
"Notes",
"-f",
"json",
])
.assert()
.success();
let answer: serde_json::Value =
serde_json::from_slice(&output.get_output().stdout).expect("stdout is JSON");
assert_eq!(
answer,
serde_json::json!({ "action": "created", "slug": "users/ilubenets/notes", "id": 4600 })
);
}