From 4d58d335676c8c19eb130226556e5539b5819869 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Mon, 16 Mar 2026 23:06:02 -0700 Subject: [PATCH] feat(e2e): add ErrorRecord, ModeConfig, diff_errors models with tests --- tests/e2e/models.py | 41 +++++++++++++++++++++++++ tests/test_e2e_helpers.py | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/e2e/models.py create mode 100644 tests/test_e2e_helpers.py diff --git a/tests/e2e/models.py b/tests/e2e/models.py new file mode 100644 index 0000000..e9f003e --- /dev/null +++ b/tests/e2e/models.py @@ -0,0 +1,41 @@ +"""Shared data models for the Peregrine E2E test harness.""" +from __future__ import annotations +import fnmatch +from dataclasses import dataclass +from pathlib import Path +from typing import Callable, Any + + +@dataclass(frozen=True) +class ErrorRecord: + type: str # "exception" | "alert" + message: str + element_html: str + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ErrorRecord): + return NotImplemented + return (self.type, self.message) == (other.type, other.message) + + def __hash__(self) -> int: + return hash((self.type, self.message)) + + +def diff_errors(before: list[ErrorRecord], after: list[ErrorRecord]) -> list[ErrorRecord]: + """Return errors in `after` that were not present in `before`.""" + before_set = set(before) + return [e for e in after if e not in before_set] + + +@dataclass +class ModeConfig: + name: str + base_url: str + auth_setup: Callable[[Any], None] + expected_failures: list[str] # fnmatch glob patterns against element labels + results_dir: Path | None + settings_tabs: list[str] # tabs expected per mode + + def matches_expected_failure(self, label: str) -> bool: + """Return True if label matches any expected_failure pattern (fnmatch).""" + return any(fnmatch.fnmatch(label, pattern) for pattern in self.expected_failures) diff --git a/tests/test_e2e_helpers.py b/tests/test_e2e_helpers.py new file mode 100644 index 0000000..e07706d --- /dev/null +++ b/tests/test_e2e_helpers.py @@ -0,0 +1,64 @@ +"""Unit tests for E2E harness models and helper utilities.""" +import fnmatch +import pytest +from unittest.mock import patch, MagicMock +import time +from tests.e2e.models import ErrorRecord, ModeConfig, diff_errors + + +def test_error_record_equality(): + a = ErrorRecord(type="exception", message="boom", element_html="
boom
") + b = ErrorRecord(type="exception", message="boom", element_html="
boom
") + assert a == b + + +def test_error_record_inequality(): + a = ErrorRecord(type="exception", message="boom", element_html="") + b = ErrorRecord(type="alert", message="boom", element_html="") + assert a != b + + +def test_diff_errors_returns_new_only(): + before = [ErrorRecord("exception", "old error", "")] + after = [ + ErrorRecord("exception", "old error", ""), + ErrorRecord("alert", "new error", ""), + ] + result = diff_errors(before, after) + assert result == [ErrorRecord("alert", "new error", "")] + + +def test_diff_errors_empty_when_no_change(): + errors = [ErrorRecord("exception", "x", "")] + assert diff_errors(errors, errors) == [] + + +def test_diff_errors_empty_before(): + after = [ErrorRecord("alert", "boom", "")] + assert diff_errors([], after) == after + + +def test_mode_config_expected_failure_match(): + config = ModeConfig( + name="demo", + base_url="http://localhost:8504", + auth_setup=lambda ctx: None, + expected_failures=["Fetch*", "Generate Cover Letter"], + results_dir=None, + settings_tabs=["👤 My Profile"], + ) + assert config.matches_expected_failure("Fetch New Jobs") + assert config.matches_expected_failure("Generate Cover Letter") + assert not config.matches_expected_failure("View Jobs") + + +def test_mode_config_no_expected_failures(): + config = ModeConfig( + name="local", + base_url="http://localhost:8502", + auth_setup=lambda ctx: None, + expected_failures=[], + results_dir=None, + settings_tabs=[], + ) + assert not config.matches_expected_failure("Fetch New Jobs")