Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/telemetry_window_demo/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def load_events(path: str | Path) -> pd.DataFrame:

def load_feature_table(path: str | Path) -> pd.DataFrame:
table_path = Path(path)
frame = pd.read_csv(table_path)
frame = _read_csv_table(table_path, table_name="feature table")
_require_columns(frame, FEATURE_TABLE_REQUIRED_COLUMNS, source=str(table_path))
_parse_datetime_columns(
frame,
Expand All @@ -105,7 +105,7 @@ def load_feature_table(path: str | Path) -> pd.DataFrame:

def load_alert_table(path: str | Path) -> pd.DataFrame:
table_path = Path(path)
frame = pd.read_csv(table_path)
frame = _read_csv_table(table_path, table_name="alert table")
_require_columns(frame, ALERT_TABLE_REQUIRED_COLUMNS, source=str(table_path))
_parse_datetime_columns(
frame,
Expand All @@ -115,6 +115,20 @@ def load_alert_table(path: str | Path) -> pd.DataFrame:
return frame


def _read_csv_table(table_path: Path, *, table_name: str) -> pd.DataFrame:
if not table_path.exists():
display_name = table_name[:1].upper() + table_name[1:]
raise FileNotFoundError(f"{display_name} not found: {table_path}")
try:
return pd.read_csv(table_path)
except (
pd.errors.EmptyDataError,
pd.errors.ParserError,
UnicodeDecodeError,
) as exc:
raise ValueError(f"Invalid {table_name} CSV in {table_path}: {exc}") from exc


def _require_columns(
frame: pd.DataFrame,
required_columns: tuple[str, ...],
Expand Down
18 changes: 18 additions & 0 deletions tests/test_cli_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,21 @@ def test_main_reports_bad_plot_feature_table_without_traceback(tmp_path, capsys)
assert stderr.startswith("error: ")
assert "event_count" in stderr
assert "Traceback" not in stderr


def test_main_reports_missing_default_alert_table_without_traceback(tmp_path, capsys) -> None:
features_path = tmp_path / "features.csv"
features_path.write_text(
"window_start,window_end,event_count,error_rate\n"
"2026-03-10T10:00:00Z,2026-03-10T10:01:00Z,10,0.25\n",
encoding="utf-8",
)

with pytest.raises(SystemExit) as excinfo:
main(["plot", "--features", str(features_path)])

assert excinfo.value.code == 1
stderr = capsys.readouterr().err
assert stderr.startswith("error: ")
assert "Alert table not found" in stderr
assert "Traceback" not in stderr
20 changes: 20 additions & 0 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ def test_load_feature_table_requires_plot_columns(tmp_path) -> None:
assert "event_count" in message


def test_load_feature_table_reports_missing_file(tmp_path) -> None:
path = tmp_path / "missing-features.csv"

with pytest.raises(FileNotFoundError, match="Feature table not found"):
load_feature_table(path)


def test_load_feature_table_rejects_invalid_window_timestamp(tmp_path) -> None:
path = tmp_path / "features.csv"
path.write_text(
Expand All @@ -192,6 +199,19 @@ def test_load_feature_table_rejects_invalid_window_timestamp(tmp_path) -> None:
assert "window_start" in message


def test_load_alert_table_rejects_invalid_csv(tmp_path) -> None:
path = tmp_path / "alerts.csv"
path.write_text(
'alert_time,window_start,window_end,rule_name,severity\n'
'"2026-03-10T10:01:00Z,2026-03-10T10:00:00Z,'
'2026-03-10T10:01:00Z,high_error_rate,medium\n',
encoding="utf-8",
)

with pytest.raises(ValueError, match="Invalid alert table CSV"):
load_alert_table(path)


def test_load_alert_table_requires_plot_columns(tmp_path) -> None:
path = tmp_path / "alerts.csv"
path.write_text(
Expand Down
Loading