✅test(reporter): add tests for reporter module
add tests, reformat code, add an output variable in writing function. All reading and writing functions must read in windows-1252 encoding because original logs are windows-1252 encoded
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from logwatcher.classifier import classify_log_entries
|
||||
from logwatcher.config import DATETIME_FORMAT, FIXTURE_PATH
|
||||
from logwatcher.models import LogEntry
|
||||
from logwatcher.parser import parse_log_file
|
||||
from logwatcher.reporter import (
|
||||
N2_SUPPORT_TEMPLATE,
|
||||
OTHER_TEMPLATE,
|
||||
_get_period,
|
||||
_render_entries,
|
||||
_render_target_report,
|
||||
build_reports,
|
||||
write_log_report,
|
||||
)
|
||||
|
||||
# path of all CR_* files used in `get_all_log_entries_fixture`
|
||||
CR_LOG_FILES = list(FIXTURE_PATH.glob("*/CR_*"))
|
||||
|
||||
|
||||
def _get_classified_log_entries(
|
||||
log_file: Path,
|
||||
) -> tuple[list[LogEntry], list[LogEntry]]:
|
||||
"""Parse a log file and split its entries into relevant and irrelevant lists."""
|
||||
log_entries = parse_log_file(log_file)
|
||||
return classify_log_entries(log_entries)
|
||||
|
||||
|
||||
@pytest.fixture(name="nb_files")
|
||||
def get_nb_log_files_used_fixture():
|
||||
"""Return the number of CR_* log files used by the other fixtures."""
|
||||
return len(CR_LOG_FILES)
|
||||
|
||||
|
||||
@pytest.fixture(name="get_all_log_entries")
|
||||
def get_all_log_entries_fixture() -> tuple[list[LogEntry], list[LogEntry]]:
|
||||
"""Parse and classify every CR_* fixture, returning (relevant, irrelevant)."""
|
||||
all_entries = []
|
||||
for log_file in CR_LOG_FILES:
|
||||
all_entries.extend(parse_log_file(log_file))
|
||||
relevant_list, irrelevant_list = classify_log_entries(all_entries)
|
||||
return relevant_list, irrelevant_list
|
||||
|
||||
|
||||
@pytest.fixture(name="get_only_relevant_log_entries")
|
||||
def get_only_relevant_log_entries_fixture(valid_log_dir: Path):
|
||||
"""Return (relevant, irrelevant) for a file containing only N2-relevant errors."""
|
||||
log_file = valid_log_dir / "only_relevant_logs.txt"
|
||||
return _get_classified_log_entries(log_file)
|
||||
|
||||
|
||||
@pytest.fixture(name="get_only_irrelevant_log_entries")
|
||||
def get_only_irrelevant_log_entries_fixture(valid_log_dir: Path):
|
||||
"""Return (relevant, irrelevant) for a file containing only non-N2 errors."""
|
||||
log_file = valid_log_dir / "no_relevant_logs.txt"
|
||||
return _get_classified_log_entries(log_file)
|
||||
|
||||
|
||||
def test_get_period_empty_list():
|
||||
"""An empty entry list yields an empty (start, end) period."""
|
||||
assert _get_period([]) == ("", "")
|
||||
|
||||
|
||||
def test_get_period_format(
|
||||
get_all_log_entries: tuple[list[LogEntry], list[LogEntry]],
|
||||
):
|
||||
"""Period bounds are formatted according to the configured datetime format."""
|
||||
period = _get_period(get_all_log_entries[0] + get_all_log_entries[1])
|
||||
for period_bound in period:
|
||||
try:
|
||||
assert datetime.strptime(period_bound, DATETIME_FORMAT) # noqa: DTZ007
|
||||
except ValueError:
|
||||
pytest.fail(f"Wrong date format for {period_bound}")
|
||||
|
||||
|
||||
def test_render_entries_all(get_all_log_entries: tuple[list[LogEntry], list[LogEntry]]):
|
||||
"""Rendered entries expose every useful field but never the raw line."""
|
||||
for log_entries in get_all_log_entries:
|
||||
error_report = _render_entries(log_entries)
|
||||
if log_entries:
|
||||
for index, log_entry in enumerate(log_entries, start=1):
|
||||
assert f"[{index}]" in error_report
|
||||
assert log_entry.error_name in error_report
|
||||
assert log_entry.store_name in error_report
|
||||
assert log_entry.get_error_time() in error_report
|
||||
try:
|
||||
assert datetime.strptime( # noqa: DTZ007
|
||||
log_entry.get_error_time(), DATETIME_FORMAT
|
||||
)
|
||||
except ValueError:
|
||||
pytest.fail(f"Wrong date format for {log_entry}")
|
||||
assert log_entry.error_message in error_report
|
||||
assert log_entry.server_ip in error_report
|
||||
assert log_entry.mdc_server_name in error_report
|
||||
# must not be in report file
|
||||
assert log_entry.raw_line not in error_report
|
||||
elif not log_entries:
|
||||
assert error_report == ""
|
||||
else:
|
||||
pytest.fail("`log_entries` must be defined")
|
||||
|
||||
|
||||
def test_render_entries_relevant(
|
||||
get_only_relevant_log_entries: tuple[list[LogEntry], list[LogEntry]],
|
||||
):
|
||||
"""Only-relevant input renders a non-empty relevant block and an empty other block."""
|
||||
error_report_relevant = _render_entries(get_only_relevant_log_entries[0])
|
||||
error_report_irrelevant = _render_entries(get_only_relevant_log_entries[1])
|
||||
assert error_report_relevant != "", error_report_irrelevant == ""
|
||||
|
||||
|
||||
def test_render_entries_irrelevant(
|
||||
get_only_irrelevant_log_entries: tuple[list[LogEntry], list[LogEntry]],
|
||||
):
|
||||
"""Only-irrelevant input renders an empty relevant block and a non-empty other block."""
|
||||
error_report_relevant = _render_entries(get_only_irrelevant_log_entries[0])
|
||||
error_report_irrelevant = _render_entries(get_only_irrelevant_log_entries[1])
|
||||
assert error_report_relevant == "", error_report_irrelevant != ""
|
||||
|
||||
|
||||
def test_render_target_report_relevant(
|
||||
get_only_relevant_log_entries: tuple[list[LogEntry], list[LogEntry]],
|
||||
):
|
||||
"""The N2 section header, count, and rendered entries are all present."""
|
||||
relevant = get_only_relevant_log_entries[0]
|
||||
target_report = _render_target_report(relevant, N2_SUPPORT_TEMPLATE)
|
||||
assert "ERREURS SUPPORT N2" in target_report
|
||||
assert target_report.find("Nombre d'erreurs\t : " + str(len(relevant))) != -1
|
||||
assert _render_entries(relevant) in target_report
|
||||
|
||||
|
||||
def test_render_target_report_irrelevant(
|
||||
get_only_irrelevant_log_entries: tuple[list[LogEntry], list[LogEntry]],
|
||||
):
|
||||
"""The "other" section header, count, and rendered entries are all present."""
|
||||
irrelevant = get_only_irrelevant_log_entries[1]
|
||||
target_report = _render_target_report(irrelevant, OTHER_TEMPLATE)
|
||||
assert "AUTRES ERREURS" in target_report
|
||||
assert target_report.find("Nombre d'erreurs\t : " + str(len(irrelevant))) != -1
|
||||
assert _render_entries(irrelevant) in target_report
|
||||
|
||||
|
||||
def test_render_target_report_all(
|
||||
get_all_log_entries: tuple[list[LogEntry], list[LogEntry]],
|
||||
):
|
||||
"""Each target template renders the expected section header."""
|
||||
relevant, irrelevant = get_all_log_entries
|
||||
relevant_report = _render_target_report(relevant, N2_SUPPORT_TEMPLATE)
|
||||
irrelevant_report = _render_target_report(irrelevant, OTHER_TEMPLATE)
|
||||
assert "ERREURS SUPPORT N2" in relevant_report
|
||||
assert "AUTRES ERREURS" in irrelevant_report
|
||||
|
||||
|
||||
def test_build_reports(
|
||||
get_all_log_entries: tuple[list[LogEntry], list[LogEntry]], nb_files
|
||||
):
|
||||
"""The three reports are built with correct headers, counts, and section separation."""
|
||||
start_date, end_date = _get_period(get_all_log_entries[0] + get_all_log_entries[1])
|
||||
reports = build_reports(
|
||||
get_all_log_entries[0], get_all_log_entries[1], start_date, end_date, nb_files
|
||||
)
|
||||
|
||||
assert "all" in reports and "n2" in reports and "other" in reports
|
||||
assert reports["all"].find(f"Période\t : {start_date} -> {end_date}") != -1
|
||||
assert reports["all"].find(f"Fichiers lu\t : {nb_files}") != -1
|
||||
assert (
|
||||
reports["all"].find(
|
||||
f"Nombre total d'erreurs\t: {len(get_all_log_entries[0] + get_all_log_entries[1])}"
|
||||
)
|
||||
!= -1
|
||||
)
|
||||
assert "ERREURS SUPPORT N2" in reports["all"] or "AUTRES ERREURS" in reports["all"]
|
||||
assert (
|
||||
"AUTRES ERREURS" not in reports["n2"] and "ERREURS SUPPORT N2" in reports["n2"]
|
||||
)
|
||||
assert (
|
||||
"ERREURS SUPPORT N2" not in reports["other"]
|
||||
and "AUTRES ERREURS" in reports["other"]
|
||||
)
|
||||
assert (
|
||||
reports["n2"].find(f"Nombre total d'erreurs\t: {len(get_all_log_entries[0])}")
|
||||
!= -1
|
||||
)
|
||||
assert (
|
||||
reports["other"].find(
|
||||
f"Nombre total d'erreurs\t: {len(get_all_log_entries[1])}"
|
||||
)
|
||||
!= -1
|
||||
)
|
||||
|
||||
|
||||
def _test_write_log_report(
|
||||
log_entries_tuple: tuple[list[LogEntry], list[LogEntry]],
|
||||
nb_files: int,
|
||||
target_name_empty: str,
|
||||
output_dir: Path,
|
||||
):
|
||||
"""Write the reports, then verify files exist and their content matches build_reports."""
|
||||
results_path = output_dir / "results"
|
||||
relevant, irrelevant = log_entries_tuple
|
||||
write_log_report(relevant, irrelevant, nb_files, results_path)
|
||||
# check if files were created
|
||||
all_file, n2_file, other_file = (
|
||||
(results_path / "all.log"),
|
||||
(results_path / "n2.log"),
|
||||
(results_path / "other.log"),
|
||||
)
|
||||
assert all_file.exists()
|
||||
assert n2_file.exists()
|
||||
assert other_file.exists()
|
||||
|
||||
# check content
|
||||
start_date, end_date = _get_period(relevant + irrelevant)
|
||||
reports = build_reports(relevant, irrelevant, start_date, end_date, nb_files)
|
||||
nb_errors = {
|
||||
"all": len(relevant + irrelevant),
|
||||
"n2": len(relevant),
|
||||
"other": len(irrelevant),
|
||||
}
|
||||
|
||||
assert reports["all"] == all_file.read_text(encoding="windows-1252")
|
||||
assert (
|
||||
all_file.read_text(encoding="windows-1252").find(
|
||||
f"Nombre total d'erreurs\t: {nb_errors['all']}"
|
||||
)
|
||||
!= -1
|
||||
)
|
||||
assert reports["n2"] == n2_file.read_text(encoding="windows-1252")
|
||||
assert (
|
||||
n2_file.read_text(encoding="windows-1252").find(
|
||||
f"Nombre total d'erreurs\t: {nb_errors['n2']}"
|
||||
)
|
||||
!= -1
|
||||
)
|
||||
assert reports["other"] == other_file.read_text(encoding="windows-1252")
|
||||
assert (
|
||||
other_file.read_text(encoding="windows-1252").find(
|
||||
f"Nombre total d'erreurs\t: {nb_errors['other']}"
|
||||
)
|
||||
!= -1
|
||||
)
|
||||
assert nb_errors[target_name_empty] == 0 # the target list must be empty
|
||||
|
||||
|
||||
def test_write_log_report_relevant(
|
||||
get_only_relevant_log_entries: tuple[list[LogEntry], list[LogEntry]],
|
||||
nb_files: int,
|
||||
tmp_path: Path,
|
||||
):
|
||||
"""Only-relevant input writes the three files with an empty "other" list."""
|
||||
_test_write_log_report(get_only_relevant_log_entries, nb_files, "other", tmp_path)
|
||||
|
||||
|
||||
def test_write_log_report_irrelevant(
|
||||
get_only_irrelevant_log_entries: tuple[list[LogEntry], list[LogEntry]],
|
||||
nb_files: int,
|
||||
tmp_path: Path,
|
||||
):
|
||||
"""Only-irrelevant input writes the three files with an empty "n2" list."""
|
||||
_test_write_log_report(get_only_irrelevant_log_entries, nb_files, "n2", tmp_path)
|
||||
|
||||
|
||||
def test_write_log_report_all_logs(
|
||||
get_all_log_entries: tuple[list[LogEntry], list[LogEntry]],
|
||||
nb_files: int,
|
||||
tmp_path: Path,
|
||||
):
|
||||
"""The full fixture writes three files whose content matches build_reports exactly."""
|
||||
results_path = tmp_path / "results"
|
||||
write_log_report(
|
||||
get_all_log_entries[0], get_all_log_entries[1], nb_files, results_path
|
||||
)
|
||||
|
||||
# check if files were created
|
||||
all_file, n2_file, other_file = (
|
||||
(results_path / "all.log"),
|
||||
(results_path / "n2.log"),
|
||||
(results_path / "other.log"),
|
||||
)
|
||||
assert all_file.exists()
|
||||
assert n2_file.exists()
|
||||
assert other_file.exists()
|
||||
|
||||
# check content
|
||||
start_date, end_date = _get_period(get_all_log_entries[0] + get_all_log_entries[1])
|
||||
reports = build_reports(
|
||||
get_all_log_entries[0], get_all_log_entries[1], start_date, end_date, nb_files
|
||||
)
|
||||
assert reports["all"] == all_file.read_text(encoding="windows-1252")
|
||||
assert reports["n2"] == n2_file.read_text(encoding="windows-1252")
|
||||
assert reports["other"] == other_file.read_text(encoding="windows-1252")
|
||||
|
||||
Reference in New Issue
Block a user