use std::error::Error;
use std::time::Duration;
use std::time::Instant;
use super::ListStreamPolicy;
use crate::directory::DirectoryStreamState;
use crate::directory::ListOptions;
use crate::directory::ListScope;
use crate::error::FsError;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::metadata::DirEntry;
use crate::metadata::FileKind;
use crate::metadata::FileSystemLimits;
use crate::path::Path;
use crate::path::PathSemantics;
fn policy(now: Instant, options: ListOptions) -> ListStreamPolicy {
ListStreamPolicy::new(
ListScope::Namespace,
options,
"clock-test",
PathSemantics::ObjectKey,
FileSystemLimits::unknown(),
now,
)
.unwrap()
}
fn entry() -> DirEntry {
DirEntry::new(Path::parse_literal("a").unwrap(), FileKind::File)
}
#[test]
fn deadline_boundary_is_inclusive() {
let now = Instant::now();
let mut stream = policy(
now,
ListOptions::object_keys().with_deadline(Some(Duration::from_millis(10))),
);
stream.before_next(now + Duration::from_millis(9)).unwrap();
assert_eq!(
stream.before_next(now + Duration::from_millis(10)).unwrap_err().kind(),
FsErrorKind::ResourceLimitExceeded
);
assert_eq!(stream.state(), DirectoryStreamState::Failed);
assert_eq!(stream.before_next(now).unwrap_err().kind(), FsErrorKind::InvalidState);
}
#[test]
fn successful_late_results_are_rejected() {
let now = Instant::now();
for value in [Some(entry()), None] {
let mut stream = policy(
now,
ListOptions::object_keys().with_deadline(Some(Duration::from_millis(10))),
);
stream.before_next(now).unwrap();
assert_eq!(
stream
.finish_next(Ok(value), now + Duration::from_millis(10))
.unwrap_err()
.kind(),
FsErrorKind::ResourceLimitExceeded
);
assert_eq!(stream.state(), DirectoryStreamState::Failed);
}
}
#[test]
fn late_provider_error_preserves_kind_path_and_source() {
let now = Instant::now();
let mut stream = policy(
now,
ListOptions::object_keys().with_deadline(Some(Duration::from_millis(10))),
);
stream.before_next(now).unwrap();
let path = Path::parse_literal("actual-key").unwrap();
let error = FsError::with_source(
FsErrorKind::PermissionDenied,
FsOperation::List,
"denied",
std::io::Error::other("provider source"),
)
.with_path(path.clone());
let error = stream
.finish_next(Err(error), now + Duration::from_secs(1))
.unwrap_err();
assert_eq!(error.kind(), FsErrorKind::PermissionDenied);
assert_eq!(error.path(), Some(&path));
assert_eq!(error.source().unwrap().to_string(), "provider source");
assert_eq!(stream.state(), DirectoryStreamState::Failed);
}
#[test]
fn empty_list_and_entry_overrun_are_distinct() {
let now = Instant::now();
let options = ListOptions::object_keys().with_max_entries(Some(0));
let mut empty = policy(now, options.clone());
empty.before_next(now).unwrap();
assert!(empty.finish_next(Ok(None), now).unwrap().is_none());
assert_eq!(empty.state(), DirectoryStreamState::Exhausted);
assert_eq!(empty.before_next(now).unwrap_err().kind(), FsErrorKind::InvalidState);
let mut nonempty = policy(now, options);
nonempty.before_next(now).unwrap();
assert_eq!(
nonempty.finish_next(Ok(Some(entry())), now).unwrap_err().kind(),
FsErrorKind::ResourceLimitExceeded
);
}
#[test]
fn entry_limit_requires_an_explicit_overrun_probe() {
let now = Instant::now();
let mut stream = policy(now, ListOptions::object_keys().with_max_entries(Some(1)));
stream.before_next(now).unwrap();
assert!(stream.finish_next(Ok(Some(entry())), now).unwrap().is_some());
stream.before_next(now).unwrap();
assert_eq!(
stream.finish_next(Ok(Some(entry())), now).unwrap_err().kind(),
FsErrorKind::ResourceLimitExceeded
);
}
#[test]
fn overflowing_deadline_is_rejected_at_construction() {
let result = ListStreamPolicy::new(
ListScope::Namespace,
ListOptions::object_keys().with_deadline(Some(Duration::MAX)),
"clock-test",
PathSemantics::ObjectKey,
FileSystemLimits::unknown(),
Instant::now(),
);
assert_eq!(result.err().unwrap().kind(), FsErrorKind::InvalidOptions);
}