"""Log parser tests (Chapter 12: highest testing priority per Ch12).""" from __future__ import annotations from app.services.log_parser.apache import combined_parser, common_parser from app.services.log_parser.format_compiler import compile_format, parse_apache_timestamp COMMON_LINE = '127.0.0.1 - - [10/Oct/2026:13:55:36 -0700] "GET /index.html HTTP/1.1" 200 1234\n' COMBINED_LINE = ( '127.0.0.1 - - [10/Oct/2026:13:55:36 -0700] "GET /index.html HTTP/1.1" 200 1234 ' '"https://example.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0"\n' ) MALFORMED_LINE = "this is not a log line\n" def test_common_parser_parses_valid_line(): entry = common_parser().parse_line(COMMON_LINE) assert entry is not None assert entry.ip == "127.0.0.1" assert entry.method == "GET" assert entry.path == "/index.html" assert entry.status_code == 200 assert entry.bytes_sent == 1234 assert entry.referrer is None assert entry.user_agent is None def test_combined_parser_parses_referrer_and_user_agent(): entry = combined_parser().parse_line(COMBINED_LINE) assert entry is not None assert entry.referrer == "https://example.com/" assert "Chrome" in entry.user_agent def test_malformed_line_returns_none_not_raises(): assert common_parser().parse_line(MALFORMED_LINE) is None def test_custom_format_string_compiles_and_parses(): custom_format = '%h %t "%r" %>s' compiled = compile_format(custom_format) match = compiled.pattern.match('127.0.0.1 [10/Oct/2026:13:55:36 -0700] "GET /x HTTP/1.1" 404') assert match is not None assert match.group("ip") == "127.0.0.1" assert match.group("status") == "404" def test_parse_apache_timestamp_normalizes_to_utc(): dt = parse_apache_timestamp("10/Oct/2026:13:55:36 -0700") assert dt.hour == 20 # -0700 -> UTC assert dt.tzinfo is None