use crate::validation::{
NameKind, RESERVED_LISTENER_RECONNECTED, RESERVED_PREFIX, validate_local_name, validate_name,
validate_shared_name,
};
use crate::{Error, Result};
fn assert_invalidname(r: Result<()>, expected: &str) {
match r {
Err(Error::InvalidName { name }) => assert_eq!(name, expected),
other => panic!("expected InvalidName({expected}), got {other:?}"),
}
}
fn assert_reservedname(r: Result<()>, expected: &str) {
match r {
Err(Error::ReservedName { name }) => assert_eq!(name, expected),
other => panic!("expected ReservedName({expected}), got {other:?}"),
}
}
#[test]
fn shared_name_accepts_legal_names() {
for legal in [
"orders",
"a",
"names with spaces",
"__not_the_reserved_prefix",
"_alkstore_x",
"__alkstor_e_",
"__ALKSTORE_lower",
"uniße-名前",
] {
validate_shared_name(legal).unwrap_or_else(|e| panic!("legal {legal:?} rejected: {e}"));
}
}
#[test]
fn shared_name_rejects_empty() {
assert_invalidname(validate_shared_name(""), "");
}
#[test]
fn shared_name_rejects_whitespace_only() {
for ws in [" ", "\t", "\n", " \t \n "] {
assert_invalidname(validate_shared_name(ws), ws);
}
}
#[test]
fn shared_name_rejects_reserved_prefix() {
for reserved in [
"__alkstore_",
"__alkstore_outbox:foo",
"__alkstore_listener_reconnected__",
] {
assert_reservedname(validate_shared_name(reserved), reserved);
}
}
#[test]
fn local_name_accepts_legal_names() {
for legal in ["worker-1", "__alkstore_my_local_consumer"] {
validate_local_name(legal).unwrap_or_else(|e| panic!("legal {legal:?} rejected: {e}"));
}
}
#[test]
fn local_name_rejects_empty_and_whitespace_only() {
assert_invalidname(validate_local_name(""), "");
for ws in [" ", "\t\n "] {
assert_invalidname(validate_local_name(ws), ws);
}
}
#[test]
fn name_kind_dispatch_via_validate_name() {
assert!(validate_name(NameKind::Shared, "__alkstore_x").is_err());
assert!(validate_name(NameKind::Local, "__alkstore_x").is_ok());
assert!(validate_name(NameKind::Shared, "ok").is_ok());
assert!(validate_name(NameKind::Local, "ok").is_ok());
}
#[test]
fn error_display_texts() {
let e = Error::PayloadTooLarge { limit: 8000 };
assert!(e.to_string().contains("8000"));
assert!(
Error::ReservedName { name: "x".into() }
.to_string()
.contains("x")
);
assert!(
Error::InvalidName { name: "y".into() }
.to_string()
.contains("y")
);
assert_eq!(
Error::Closed.to_string(),
"the receiver or source is closed"
);
assert!(
Error::Codec("bad utf8 at 3".into())
.to_string()
.contains("bad utf8")
);
let db = std::io::Error::other("boom");
let e = Error::Database(Box::new(db));
assert!(e.to_string().contains("database"));
let src = std::error::Error::source(&e).expect("Database carries #[source]");
assert_eq!(src.to_string(), "boom");
}
#[test]
fn error_display_escapes_consumer_control_chars() {
let forged = "in\u{1}ject\n\u{7}end\t";
for e in [
Error::InvalidName {
name: forged.into(),
},
Error::ReservedName {
name: forged.into(),
},
Error::InvalidSpec {
spec: forged.into(),
},
Error::Codec(forged.into()),
] {
let s = e.to_string();
assert!(
!s.chars().any(char::is_control),
"raw control byte in rendered error: {s:?}"
);
}
assert_eq!(
Error::InvalidName {
name: "x\u{1}\ny".into()
}
.to_string(),
"invalid name `x\\u{1}\\ny`"
);
for plain in ["orders", "worker-1", "job row payload must be a string"] {
assert!(
Error::InvalidName { name: plain.into() }
.to_string()
.contains(plain)
);
}
}
#[test]
fn error_database_constructor() {
let e = Error::database(std::io::Error::other("disk gone"));
match e {
Error::Database(source) => {
assert_eq!(source.to_string(), "disk gone");
}
other => panic!("expected Database, got {other:?}"),
}
}
#[test]
fn reserved_constants_exact() {
assert_eq!(RESERVED_PREFIX, "__alkstore_");
assert_eq!(
RESERVED_LISTENER_RECONNECTED,
"__alkstore_listener_reconnected__"
);
}
#[test]
fn scheduler_error_variants() {
let e = Error::InvalidSpec {
spec: "0 0 * * *".into(),
};
assert!(e.to_string().contains("0 0 * * *"));
assert_eq!(
Error::LeadershipLost.to_string(),
"scheduler leadership lost"
);
}