Files
learn-rust/lessons/0006-errors-spec.rs

105 lines
3.3 KiB
Rust

// The 0006 specification, as executable tests. Do not edit this file — make it pass.
// Copy to: tasks/tests/errors.rs
// Run with: cargo test
use std::error::Error;
use tasks::command::Command;
use tasks::error::TaskError;
use tasks::store::Store;
use tasks::task::Priority;
fn args(list: &[&str]) -> Vec<String> {
list.iter().map(|s| s.to_string()).collect()
}
// ---------- one variant per way of failing ----------
#[test]
fn parse_errors_name_the_exact_failure() {
assert_eq!(Command::parse(&args(&[])).unwrap_err(), TaskError::NoCommand);
assert_eq!(
Command::parse(&args(&["fly"])).unwrap_err(),
TaskError::UnknownCommand("fly".to_string())
);
assert_eq!(
Command::parse(&args(&["add"])).unwrap_err(),
TaskError::MissingTitle
);
assert_eq!(
Command::parse(&args(&["add", "x", "urgent"])).unwrap_err(),
TaskError::BadPriority("urgent".to_string())
);
assert_eq!(
Command::parse(&args(&["done"])).unwrap_err(),
TaskError::MissingId
);
}
#[test]
fn store_reports_which_id_was_missing() {
let mut store = Store::new();
store.add("only task", Priority::Low);
assert_eq!(store.complete(99).unwrap_err(), TaskError::NotFound(99));
assert_eq!(store.remove(7).unwrap_err(), TaskError::NotFound(7));
}
// ---------- Display: the sentence the user reads ----------
#[test]
fn messages_are_lowercase_and_unpunctuated() {
assert_eq!(TaskError::NoCommand.to_string(), "no command given");
assert_eq!(
TaskError::UnknownCommand("fly".to_string()).to_string(),
"unknown command: fly"
);
assert_eq!(TaskError::MissingTitle.to_string(), "add needs a title");
assert_eq!(
TaskError::MissingId.to_string(),
"this command needs a task id"
);
assert_eq!(
TaskError::BadPriority("urgent".to_string()).to_string(),
"unknown priority: urgent"
);
assert_eq!(TaskError::NotFound(9).to_string(), "no task with id 9");
}
// ---------- From + ? : the parse error is wrapped, not thrown away ----------
#[test]
fn a_bad_id_wraps_the_parse_error() {
let err = Command::parse(&args(&["done", "abc"])).unwrap_err();
assert!(matches!(err, TaskError::BadId(_)), "expected BadId");
assert!(
Command::parse(&args(&["remove", "-1"])).is_err(),
"a negative id is not a u32"
);
}
#[test]
fn a_wrapped_error_is_reported_once_and_reachable() {
let err = Command::parse(&args(&["done", "abc"])).unwrap_err();
// Display says your sentence and does not repeat std's.
assert_eq!(err.to_string(), "task id must be a number");
// std's sentence is still reachable, through Error::source().
let inner = err.source().expect("BadId must expose its source");
assert_eq!(inner.to_string(), "invalid digit found in string");
}
#[test]
fn errors_that_carry_nothing_have_no_source() {
assert!(TaskError::NoCommand.source().is_none());
assert!(TaskError::NotFound(1).source().is_none());
}
// ---------- the Error trait, and the bounds the ecosystem expects ----------
#[test]
fn task_error_is_a_real_error() {
fn assert_usable_as_error<E: Error + Send + Sync + 'static>() {}
assert_usable_as_error::<TaskError>();
let boxed: Box<dyn Error> = Box::new(TaskError::NotFound(1));
assert_eq!(boxed.to_string(), "no task with id 1");
}