use super::*;
use std::io::Cursor;
use tempfile::{NamedTempFile, TempDir};
fn input_for(path: &std::path::Path) -> Vec<u8> {
serde_json::json!({ "path": path }).to_string().into_bytes()
}
fn sparse_file(size: u64) -> NamedTempFile {
use std::io::{Seek, SeekFrom, Write};
let f = NamedTempFile::new().unwrap();
let mut handle = std::fs::OpenOptions::new()
.write(true)
.open(f.path())
.unwrap();
handle.seek(SeekFrom::Start(size - 1)).unwrap();
handle.write_all(&[0u8]).unwrap();
handle.sync_all().unwrap();
f
}
#[test]
fn happy_path_reads_file_to_stdout() {
let f = NamedTempFile::new().unwrap();
std::fs::write(f.path(), b"hello bytes").unwrap();
let mut stdin = Cursor::new(input_for(f.path()));
let mut stdout = Vec::new();
run(&mut stdin, &mut stdout).unwrap();
assert_eq!(stdout, b"hello bytes");
}
#[test]
fn empty_file_yields_empty_stdout() {
let f = NamedTempFile::new().unwrap();
let mut stdin = Cursor::new(input_for(f.path()));
let mut stdout = Vec::new();
run(&mut stdin, &mut stdout).unwrap();
assert!(stdout.is_empty());
}
#[test]
fn invalid_json_input_surfaces_invalid_json() {
let mut stdin = Cursor::new(b"not json".to_vec());
let mut stdout = Vec::new();
let err = run(&mut stdin, &mut stdout).unwrap_err();
assert!(matches!(err, Error::InvalidJson(_)), "{err}");
}
#[test]
fn missing_path_field_surfaces_invalid_json() {
let mut stdin = Cursor::new(br#"{"other": "field"}"#.to_vec());
let mut stdout = Vec::new();
let err = run(&mut stdin, &mut stdout).unwrap_err();
assert!(matches!(err, Error::InvalidJson(_)), "{err}");
}
#[test]
fn extra_fields_rejected_by_deny_unknown_fields() {
let mut stdin = Cursor::new(br#"{"path": "/tmp/x", "extra": 1}"#.to_vec());
let mut stdout = Vec::new();
let err = run(&mut stdin, &mut stdout).unwrap_err();
assert!(matches!(err, Error::InvalidJson(_)), "{err}");
}
#[test]
fn missing_file_surfaces_open_error() {
let dir = TempDir::new().unwrap();
let nope = dir.path().join("does-not-exist");
let mut stdin = Cursor::new(input_for(&nope));
let mut stdout = Vec::new();
let err = run(&mut stdin, &mut stdout).unwrap_err();
let msg = err.to_string();
assert!(matches!(err, Error::Open { .. }), "{msg}");
assert!(msg.contains("does-not-exist"), "{msg}");
}
#[test]
fn directory_path_surfaces_read_or_open_error() {
let dir = TempDir::new().unwrap();
let mut stdin = Cursor::new(input_for(dir.path()));
let mut stdout = Vec::new();
let err = run(&mut stdin, &mut stdout).unwrap_err();
assert!(
matches!(err, Error::Open { .. } | Error::Read { .. }),
"{err}",
);
}
#[test]
fn oversized_file_surfaces_too_large() {
let true_size = MAX_BYTES * 7 + 3;
let f = sparse_file(true_size);
let mut stdin = Cursor::new(input_for(f.path()));
let mut stdout = Vec::new();
let err = run(&mut stdin, &mut stdout).unwrap_err();
let msg = err.to_string();
assert!(
matches!(err, Error::TooLarge { size, cap, .. } if size == true_size && cap == MAX_BYTES),
"{msg}",
);
assert!(msg.contains(&format!("is {true_size} bytes")), "{msg}");
assert!(msg.contains(&format!("cap {MAX_BYTES}")), "{msg}");
}
#[test]
fn oversize_sizes_are_the_files_own_not_the_capped_read_length() {
let msg_for = |size: u64| {
let f = sparse_file(size);
let mut stdin = Cursor::new(input_for(f.path()));
let mut stdout = Vec::new();
run(&mut stdin, &mut stdout).unwrap_err().to_string()
};
let small = msg_for(MAX_BYTES + 1);
let large = msg_for(MAX_BYTES * 4);
assert!(
small.contains(&format!("is {} bytes", MAX_BYTES + 1)),
"{small}"
);
assert!(
large.contains(&format!("is {} bytes", MAX_BYTES * 4)),
"{large}"
);
}
#[test]
fn stdin_read_error_surfaces_stdin_read() {
struct BrokenReader;
impl Read for BrokenReader {
fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
Err(io::Error::other("stdin pipe broken"))
}
}
let mut stdin = BrokenReader;
let mut stdout = Vec::new();
let err = run(&mut stdin, &mut stdout).unwrap_err();
assert!(matches!(err, Error::StdinRead(_)), "{err}");
}
#[test]
fn stdout_write_error_surfaces_write() {
struct BrokenWriter;
impl Write for BrokenWriter {
fn write(&mut self, _: &[u8]) -> io::Result<usize> {
Err(io::Error::other("stdout pipe closed"))
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
let f = NamedTempFile::new().unwrap();
std::fs::write(f.path(), b"non-empty").unwrap();
let mut stdin = Cursor::new(input_for(f.path()));
let mut stdout = BrokenWriter;
let err = run(&mut stdin, &mut stdout).unwrap_err();
assert!(matches!(err, Error::Write(_)), "{err}");
}