from pathlib import Path
import pytest
from optix.io_utils import (
ensure_existing_file,
open_file,
walk_files,
sanitize_csv_value,
PATH_MAX_LENGTH,
)
class TestEnsureExistingFile:
def test_normal_file(self, tmp_path):
f = tmp_path / "test.txt"
f.write_text("hello")
result = ensure_existing_file(f)
assert result.exists()
assert result.is_file()
def test_file_not_found(self, tmp_path):
with pytest.raises(FileNotFoundError):
ensure_existing_file(tmp_path / "nonexistent.txt")
def test_path_is_directory(self, tmp_path):
with pytest.raises(ValueError, match="Expect a file"):
ensure_existing_file(tmp_path)
def test_file_too_large(self, tmp_path):
f = tmp_path / "big.txt"
f.write_text("x")
with pytest.raises(ValueError, match="File is too large"):
ensure_existing_file(f, max_size=0)
def test_max_size_none_skips_check(self, tmp_path):
f = tmp_path / "test.txt"
f.write_text("hello")
result = ensure_existing_file(f, max_size=None)
assert result.exists()
def test_path_exceeds_path_max(self, tmp_path):
long_name = "a" * (PATH_MAX_LENGTH + 1)
with pytest.raises(ValueError, match="File path exceeds PATH_MAX"):
ensure_existing_file(Path(long_name))
def test_symlink_resolved(self, tmp_path):
real_file = tmp_path / "real.txt"
real_file.write_text("content")
link = tmp_path / "link.txt"
try:
link.symlink_to(real_file)
except OSError:
pytest.skip("Symlink not supported on this platform")
result = ensure_existing_file(link)
assert result == real_file.resolve()
class TestOpenFile:
def test_read_existing_file(self, tmp_path):
f = tmp_path / "test.txt"
f.write_text("hello", encoding="utf-8")
with open_file(f, "r") as fh:
assert fh.read() == "hello"
def test_read_nonexistent_raises(self, tmp_path):
with pytest.raises(FileNotFoundError):
open_file(tmp_path / "nope.txt", "r")
def test_write_mode(self, tmp_path):
f = tmp_path / "out.txt"
with open_file(f, "w") as fh:
fh.write("data")
assert f.read_text(encoding="utf-8") == "data"
def test_binary_mode(self, tmp_path):
f = tmp_path / "bin.dat"
f.write_bytes(b"\x00\x01\x02")
with open_file(f, "rb") as fh:
assert fh.read() == b"\x00\x01\x02"
def test_read_plus_mode_no_check(self, tmp_path):
f = tmp_path / "rw.txt"
f.write_text("init")
with open_file(f, "r+") as fh:
content = fh.read()
assert content == "init"
class TestWalkFiles:
def test_walk_directory(self, tmp_path):
(tmp_path / "a.txt").write_text("a")
sub = tmp_path / "sub"
sub.mkdir()
(sub / "b.txt").write_text("b")
files = list(walk_files(tmp_path))
assert len(files) == 2
def test_walk_single_file(self, tmp_path):
f = tmp_path / "single.txt"
f.write_text("x")
files = list(walk_files(f))
assert len(files) == 1
assert files[0] == f.resolve()
def test_walk_nonexistent(self, tmp_path):
files = list(walk_files(tmp_path / "nope"))
assert files == []
def test_walk_skips_symlinks(self, tmp_path):
real = tmp_path / "real.txt"
real.write_text("content")
link = tmp_path / "link.txt"
try:
link.symlink_to(real)
except OSError:
pytest.skip("Symlink not supported")
files = list(walk_files(tmp_path))
assert len(files) == 1
assert files[0] == real.resolve()
class TestSanitizeCsvValue:
def test_formula_prefix_equals(self):
assert sanitize_csv_value("=cmd") == "'=cmd"
def test_formula_prefix_plus(self):
assert sanitize_csv_value("+1") == "'+1"
def test_formula_prefix_minus(self):
assert sanitize_csv_value("-1") == "'-1"
def test_formula_prefix_at(self):
assert sanitize_csv_value("@sum") == "'@sum"
def test_normal_string(self):
assert sanitize_csv_value("hello") == "hello"
def test_non_string_value(self):
assert sanitize_csv_value(123) == 123
assert sanitize_csv_value(None) is None
class TestOpenFileAdditional:
def test_write_binary_mode(self, tmp_path):
f = tmp_path / "bin_out.dat"
with open_file(f, "wb") as fh:
fh.write(b"\x00\x01\x02")
assert f.read_bytes() == b"\x00\x01\x02"
def test_append_mode(self, tmp_path):
f = tmp_path / "append.txt"
f.write_text("start", encoding="utf-8")
with open_file(f, "a") as fh:
fh.write("_end")
assert f.read_text(encoding="utf-8") == "start_end"
def test_open_with_encoding(self, tmp_path):
f = tmp_path / "enc.txt"
f.write_text("test", encoding="utf-8")
with open_file(f, "r", encoding="utf-8") as fh:
assert fh.read() == "test"
class TestWalkFilesAdditional:
def test_walk_empty_directory(self, tmp_path):
sub = tmp_path / "empty"
sub.mkdir()
files = list(walk_files(sub))
assert files == []
def test_walk_nested_directories(self, tmp_path):
(tmp_path / "a" / "b").mkdir(parents=True)
(tmp_path / "a" / "b" / "deep.txt").write_text("deep")
(tmp_path / "top.txt").write_text("top")
files = list(walk_files(tmp_path))
assert len(files) == 2
def test_walk_skips_symlink_in_subdir(self, tmp_path):
sub = tmp_path / "sub"
sub.mkdir()
real = sub / "real.txt"
real.write_text("content")
link = sub / "link.txt"
try:
link.symlink_to(real)
except OSError:
pytest.skip("Symlink not supported")
files = list(walk_files(tmp_path))
resolved_files = [f.name for f in files]
assert "real.txt" in resolved_files
assert sum(1 for f in files if f.name == "real.txt") == 1
class TestEnsureExistingFileAdditional:
def test_resolve_removes_dots(self, tmp_path):
f = tmp_path / "sub" / ".." / "test.txt"
(tmp_path / "test.txt").write_text("hello")
result = ensure_existing_file(f)
assert result == (tmp_path / "test.txt").resolve()