mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
460 lines
19 KiB
Python
460 lines
19 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import date, timedelta
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from click.testing import CliRunner
|
|
from insightscli_commands.quarantine import core
|
|
from insightscli_commands.quarantine.cli import quarantine
|
|
from insightscli_commands.quarantine.pytest_support import apply_quarantine_markers
|
|
|
|
TODAY = date(2026, 6, 10)
|
|
|
|
|
|
def make_entry(**overrides: Any) -> core.Entry:
|
|
defaults: dict[str, Any] = {
|
|
"id": "insights/api/test/test_foo.py::TestFoo::test_bar",
|
|
"added": TODAY,
|
|
"expires": TODAY + timedelta(days=14),
|
|
"reason": "flaky",
|
|
"owner": "@team-devex",
|
|
}
|
|
return core.Entry(**{**defaults, **overrides})
|
|
|
|
|
|
def write_file(path: Path, entries: list[dict[str, Any]], version: int = 1) -> Path:
|
|
path.write_text(json.dumps({"version": version, "entries": entries}, indent=4) + "\n")
|
|
return path
|
|
|
|
|
|
def raw_entry(**overrides: Any) -> dict[str, Any]:
|
|
defaults: dict[str, Any] = {
|
|
"id": "insights/api/test/test_foo.py::TestFoo::test_bar",
|
|
"runner": "pytest",
|
|
"reason": "flaky",
|
|
"owner": "@team-devex",
|
|
"added": core.today_utc().isoformat(),
|
|
"expires": (core.today_utc() + timedelta(days=14)).isoformat(),
|
|
"mode": "run",
|
|
}
|
|
return {**defaults, **overrides}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"selector, test_id, expected",
|
|
[
|
|
# exact nodeid
|
|
("insights/api/test/test_foo.py::TestFoo::test_bar", "insights/api/test/test_foo.py::TestFoo::test_bar", True),
|
|
# function selector covers parameterized variants
|
|
(
|
|
"insights/api/test/test_foo.py::TestFoo::test_bar",
|
|
"insights/api/test/test_foo.py::TestFoo::test_bar[a-1]",
|
|
True,
|
|
),
|
|
# class prefix
|
|
("insights/api/test/test_foo.py::TestFoo", "insights/api/test/test_foo.py::TestFoo::test_other", True),
|
|
# file prefix
|
|
("insights/api/test/test_foo.py", "insights/api/test/test_foo.py::TestBar::test_x", True),
|
|
# directory prefix, with and without trailing slash
|
|
("insights/api/test", "insights/api/test/test_foo.py::TestFoo::test_bar", True),
|
|
("insights/api/test/", "insights/api/test/test_foo.py::TestFoo::test_bar", True),
|
|
# partial names never match
|
|
("insights/api/test", "insights/api/test_organization.py::test_x", False),
|
|
(
|
|
"insights/api/test/test_foo.py::TestFoo::test_bar",
|
|
"insights/api/test/test_foo.py::TestFoo::test_barbell",
|
|
False,
|
|
),
|
|
# product selector translates dashes to the underscored directory
|
|
("product:batch-exports", "products/batch_exports/backend/tests/test_runs.py::test_x", True),
|
|
("product:batch-exports", "products/batch_exports_v2/backend/tests/test_runs.py::test_x", False),
|
|
("product:batch-exports", "insights/api/test/test_foo.py::test_x", False),
|
|
# unrelated paths
|
|
("insights/api/test/test_foo.py", "insights/api/test/test_food.py::test_x", False),
|
|
# jest ids: file::<space-joined test name>; a file selector covers every test in it
|
|
("frontend/src/x.test.ts", "frontend/src/x.test.ts::MyLogic loads data", True),
|
|
# describe-block prefix matches via the space boundary
|
|
("frontend/src/x.test.ts::MyLogic", "frontend/src/x.test.ts::MyLogic loads data", True),
|
|
# exact full test name
|
|
("frontend/src/x.test.ts::MyLogic loads data", "frontend/src/x.test.ts::MyLogic loads data", True),
|
|
# a partial describe word never matches (space, not substring)
|
|
("frontend/src/x.test.ts::MyLog", "frontend/src/x.test.ts::MyLogic loads data", False),
|
|
],
|
|
)
|
|
def test_selector_matches(selector: str, test_id: str, expected: bool) -> None:
|
|
assert core.selector_matches(selector, test_id) is expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"selector, runner, valid",
|
|
[
|
|
("frontend/src/x.test.ts", "jest", True),
|
|
("frontend/src/x.test.ts::MyLogic loads data", "jest", True), # spaces allowed after ::
|
|
("frontend/src", "jest", True),
|
|
("product:batch-exports", "jest", True), # product rule shared with pytest
|
|
("/abs/x.test.ts", "jest", False), # absolute path
|
|
("frontend/src/x test.ts::name", "jest", False), # whitespace in the path part
|
|
("::name-only", "jest", False), # missing file path before ::
|
|
("product:batch_exports", "jest", False), # underscored product form
|
|
("playwright/e2e/login.spec.ts::Login redirects home", "playwright", True), # spaces allowed after ::
|
|
("playwright/e2e/login file.spec.ts::Login", "playwright", False), # whitespace in the path part
|
|
("anything at all", "some-future-runner", True), # unadapted runner: not validated
|
|
],
|
|
)
|
|
def test_validate_selector_by_runner(selector: str, runner: str, valid: bool) -> None:
|
|
assert (core.validate_selector(selector, runner) is None) is valid
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"expires_offset_days, expected_active",
|
|
[(-1, False), (0, True), (1, True)],
|
|
)
|
|
def test_expiry_boundary(expires_offset_days: int, expected_active: bool) -> None:
|
|
entry = make_entry(expires=TODAY + timedelta(days=expires_offset_days))
|
|
assert core.is_active(entry, TODAY) is expected_active
|
|
|
|
|
|
def test_active_entries_filters_runner_and_expiry() -> None:
|
|
entries = [
|
|
make_entry(id="a"),
|
|
make_entry(id="b", expires=TODAY - timedelta(days=1)),
|
|
make_entry(id="c", runner="jest"),
|
|
make_entry(id="d", runner="some-future-runner"),
|
|
]
|
|
assert [e.id for e in core.active_entries(entries, "pytest", TODAY)] == ["a"]
|
|
|
|
|
|
def test_load_missing_file_is_empty_and_clean(tmp_path: Path) -> None:
|
|
result = core.load(tmp_path / "nope.json")
|
|
assert result.entries == []
|
|
assert result.errors == []
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"text, error_fragment",
|
|
[
|
|
("not json{", "invalid JSON"),
|
|
("[]", "top level"),
|
|
('{"version": 2, "entries": []}', "unsupported version"),
|
|
('{"version": 1, "entries": {}}', "must be a list"),
|
|
],
|
|
)
|
|
def test_parse_malformed_file_reports_error_without_entries(text: str, error_fragment: str) -> None:
|
|
result = core.parse(text)
|
|
assert result.entries == []
|
|
assert any(error_fragment in e for e in result.errors)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"broken",
|
|
[
|
|
{"id": "", "added": "2026-06-10", "expires": "2026-06-20"},
|
|
{"id": "x", "added": "not-a-date", "expires": "2026-06-20"},
|
|
{"id": "x", "added": "2026-06-10", "expires": "2026-06-20", "mode": "pause"},
|
|
{"id": "x", "added": "2026-06-10", "expires": "2026-06-20", "reason": 42},
|
|
"not-an-object",
|
|
],
|
|
)
|
|
def test_parse_skips_broken_entry_but_keeps_good_ones(broken: Any) -> None:
|
|
good = {"id": "insights/a.py", "added": "2026-06-10", "expires": "2026-06-20"}
|
|
result = core.parse(json.dumps({"version": 1, "entries": [broken, good]}))
|
|
assert [e.id for e in result.entries] == ["insights/a.py"]
|
|
assert len(result.errors) == 1
|
|
|
|
|
|
def test_parse_warns_on_unknown_field_and_preserves_it_on_render() -> None:
|
|
entry = {"id": "insights/a.py", "added": "2026-06-10", "expires": "2026-06-20", "future_field": "x"}
|
|
result = core.parse(json.dumps({"version": 1, "entries": [entry], "future_top_level": True}))
|
|
assert result.errors == []
|
|
assert any("future_field" in w for w in result.warnings)
|
|
rendered = json.loads(core.render(result.entries, result.extras))
|
|
assert rendered["future_top_level"] is True
|
|
assert rendered["entries"][0]["future_field"] == "x"
|
|
|
|
|
|
def test_render_is_sorted_indented_and_newline_terminated() -> None:
|
|
text = core.render([make_entry(id="z/b.py"), make_entry(id="a/a.py")])
|
|
assert text.endswith("}\n")
|
|
assert ' "version": 1' in text
|
|
ids = [e["id"] for e in json.loads(text)["entries"]]
|
|
assert ids == sorted(ids)
|
|
|
|
|
|
# ---------- pytest adapter ----------
|
|
|
|
|
|
class FakeItem:
|
|
def __init__(self, nodeid: str) -> None:
|
|
self.nodeid = nodeid
|
|
self.markers: list[Any] = []
|
|
|
|
def add_marker(self, marker: Any) -> None:
|
|
self.markers.append(marker)
|
|
|
|
def get_closest_marker(self, name: str) -> Any:
|
|
return next((m for m in self.markers if m.name == name), None)
|
|
|
|
def marker_names(self) -> list[str]:
|
|
return [m.name for m in self.markers]
|
|
|
|
|
|
def test_adapter_marks_matching_item_xfail(tmp_path: Path) -> None:
|
|
path = write_file(tmp_path / "q.json", [raw_entry()])
|
|
item = FakeItem("insights/api/test/test_foo.py::TestFoo::test_bar")
|
|
other = FakeItem("insights/api/test/test_other.py::test_x")
|
|
apply_quarantine_markers([item, other], path=path) # type: ignore[arg-type]
|
|
assert item.marker_names() == ["quarantine", "xfail"]
|
|
assert "quarantined until" in item.markers[1].kwargs["reason"]
|
|
assert other.markers == []
|
|
|
|
|
|
def test_adapter_skip_mode_applies_skip_marker(tmp_path: Path) -> None:
|
|
path = write_file(tmp_path / "q.json", [raw_entry(mode="skip")])
|
|
item = FakeItem("insights/api/test/test_foo.py::TestFoo::test_bar")
|
|
apply_quarantine_markers([item], path=path) # type: ignore[arg-type]
|
|
assert item.marker_names() == ["quarantine", "skip"]
|
|
|
|
|
|
def test_adapter_most_specific_selector_wins(tmp_path: Path) -> None:
|
|
entries = [
|
|
raw_entry(id="insights/api/test/test_foo.py", mode="run"),
|
|
raw_entry(id="insights/api/test/test_foo.py::TestFoo::test_bar", mode="skip"),
|
|
]
|
|
path = write_file(tmp_path / "q.json", entries)
|
|
narrow = FakeItem("insights/api/test/test_foo.py::TestFoo::test_bar")
|
|
broad = FakeItem("insights/api/test/test_foo.py::TestFoo::test_other")
|
|
apply_quarantine_markers([narrow, broad], path=path) # type: ignore[arg-type]
|
|
assert narrow.marker_names() == ["quarantine", "skip"]
|
|
assert broad.marker_names() == ["quarantine", "xfail"]
|
|
|
|
|
|
def test_adapter_is_idempotent_across_double_registration(tmp_path: Path) -> None:
|
|
path = write_file(tmp_path / "q.json", [raw_entry()])
|
|
item = FakeItem("insights/api/test/test_foo.py::TestFoo::test_bar")
|
|
apply_quarantine_markers([item], path=path) # type: ignore[arg-type]
|
|
apply_quarantine_markers([item], path=path) # type: ignore[arg-type]
|
|
assert item.marker_names() == ["quarantine", "xfail"]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"entry",
|
|
[
|
|
raw_entry(expires="2020-01-01"), # expired → inert
|
|
raw_entry(runner="jest"), # other runner → not pytest's business
|
|
raw_entry(runner="never-heard-of-it"), # unknown runner → ignored, not an error
|
|
],
|
|
)
|
|
def test_adapter_leaves_item_unmarked(tmp_path: Path, entry: dict[str, Any]) -> None:
|
|
path = write_file(tmp_path / "q.json", [entry])
|
|
item = FakeItem("insights/api/test/test_foo.py::TestFoo::test_bar")
|
|
apply_quarantine_markers([item], path=path) # type: ignore[arg-type]
|
|
assert item.markers == []
|
|
|
|
|
|
def test_adapter_fails_open_on_malformed_file(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
|
path = tmp_path / "q.json"
|
|
path.write_text("not json{")
|
|
item = FakeItem("insights/api/test/test_foo.py::TestFoo::test_bar")
|
|
apply_quarantine_markers([item], path=path) # type: ignore[arg-type]
|
|
assert item.markers == []
|
|
assert "[quarantine]" in capsys.readouterr().err
|
|
|
|
|
|
# ---------- CLI ----------
|
|
|
|
|
|
@pytest.fixture
|
|
def runner() -> CliRunner:
|
|
return CliRunner()
|
|
|
|
|
|
def cli(runner: CliRunner, path: Path, *args: str) -> Any:
|
|
return runner.invoke(quarantine, ["--file", str(path), *args])
|
|
|
|
|
|
def test_add_creates_canonical_file(runner: CliRunner, tmp_path: Path) -> None:
|
|
path = tmp_path / "q.json"
|
|
result = cli(runner, path, "add", "insights/api/test/test_foo.py", "--reason", "flaky", "--owner", "@team-devex")
|
|
assert result.exit_code == 0, result.output
|
|
text = path.read_text()
|
|
assert text.endswith("\n")
|
|
data = json.loads(text)
|
|
entry = data["entries"][0]
|
|
assert entry["id"] == "insights/api/test/test_foo.py"
|
|
assert entry["mode"] == "run"
|
|
assert date.fromisoformat(entry["expires"]) - date.fromisoformat(entry["added"]) == timedelta(days=14)
|
|
|
|
|
|
def test_add_records_jest_runner(runner: CliRunner, tmp_path: Path) -> None:
|
|
path = tmp_path / "q.json"
|
|
result = cli(
|
|
runner, path, "add", "frontend/src/x.test.ts", "--runner", "jest", "--reason", "flaky", "--owner", "@web"
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
entry = json.loads(path.read_text())["entries"][0]
|
|
assert (entry["runner"], entry["id"]) == ("jest", "frontend/src/x.test.ts")
|
|
|
|
|
|
def test_add_replaces_existing_entry_with_same_id(runner: CliRunner, tmp_path: Path) -> None:
|
|
path = write_file(tmp_path / "q.json", [raw_entry(reason="old")])
|
|
result = cli(runner, path, "add", raw_entry()["id"], "--reason", "new", "--owner", "@x", "--mode", "skip")
|
|
assert result.exit_code == 0, result.output
|
|
entries = json.loads(path.read_text())["entries"]
|
|
assert len(entries) == 1
|
|
assert entries[0]["reason"] == "new"
|
|
assert entries[0]["mode"] == "skip"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"argv",
|
|
[
|
|
("add", "x.py", "--reason", "r", "--owner", "@o", "--days", "31"), # over the cap
|
|
("add", "x.py", "--reason", "r", "--owner", "@o", "--days", "0"),
|
|
("add", "/abs/path.py", "--reason", "r", "--owner", "@o"), # absolute selector
|
|
("add", "product:no_such_product", "--reason", "r", "--owner", "@o"), # underscored / unknown product
|
|
("add", "x.py", "--owner", "@o"), # missing --reason
|
|
],
|
|
)
|
|
def test_add_rejects_invalid_input(runner: CliRunner, tmp_path: Path, argv: tuple[str, ...]) -> None:
|
|
path = tmp_path / "q.json"
|
|
result = cli(runner, path, *argv)
|
|
assert result.exit_code != 0
|
|
assert not path.exists()
|
|
|
|
|
|
def test_add_refuses_to_rewrite_malformed_file(runner: CliRunner, tmp_path: Path) -> None:
|
|
path = tmp_path / "q.json"
|
|
path.write_text("not json{")
|
|
result = cli(runner, path, "add", "x.py", "--reason", "r", "--owner", "@o")
|
|
assert result.exit_code != 0
|
|
assert path.read_text() == "not json{"
|
|
|
|
|
|
def test_remove_absent_id_succeeds(runner: CliRunner, tmp_path: Path) -> None:
|
|
path = write_file(tmp_path / "q.json", [raw_entry()])
|
|
result = cli(runner, path, "remove", "insights/never/was/quarantined.py")
|
|
assert result.exit_code == 0
|
|
assert "nothing to do" in result.output
|
|
assert len(json.loads(path.read_text())["entries"]) == 1
|
|
|
|
|
|
def test_remove_deletes_entry(runner: CliRunner, tmp_path: Path) -> None:
|
|
path = write_file(tmp_path / "q.json", [raw_entry()])
|
|
result = cli(runner, path, "remove", raw_entry()["id"])
|
|
assert result.exit_code == 0
|
|
assert json.loads(path.read_text())["entries"] == []
|
|
|
|
|
|
def test_list_json_emits_canonical_document(runner: CliRunner, tmp_path: Path) -> None:
|
|
path = write_file(tmp_path / "q.json", [raw_entry()])
|
|
result = cli(runner, path, "list", "--json")
|
|
assert result.exit_code == 0
|
|
assert json.loads(result.output)["version"] == 1
|
|
|
|
|
|
def test_list_shows_status(runner: CliRunner, tmp_path: Path) -> None:
|
|
path = write_file(tmp_path / "q.json", [raw_entry()])
|
|
result = cli(runner, path, "list")
|
|
assert result.exit_code == 0
|
|
assert "expires in" in result.output
|
|
assert "@team-devex" in result.output
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"entries, expected_exit, expected_fragment",
|
|
[
|
|
# healthy file
|
|
([raw_entry()], 0, "OK"),
|
|
# duplicate ids
|
|
([raw_entry(), raw_entry(reason="again")], 1, "duplicate id"),
|
|
# cap exceeded
|
|
([raw_entry(expires=(core.today_utc() + timedelta(days=40)).isoformat())], 1, "exceeds 30 days"),
|
|
# expires before added
|
|
([raw_entry(expires=(core.today_utc() - timedelta(days=1)).isoformat())], 1, "before added"),
|
|
# expired beyond the grace period
|
|
(
|
|
[
|
|
raw_entry(
|
|
added=(core.today_utc() - timedelta(days=30)).isoformat(),
|
|
expires=(core.today_utc() - timedelta(days=10)).isoformat(),
|
|
)
|
|
],
|
|
1,
|
|
"remove or re-triage",
|
|
),
|
|
# expired but within grace → warn, pass
|
|
(
|
|
[
|
|
raw_entry(
|
|
added=(core.today_utc() - timedelta(days=20)).isoformat(),
|
|
expires=(core.today_utc() - timedelta(days=3)).isoformat(),
|
|
)
|
|
],
|
|
0,
|
|
"remove within",
|
|
),
|
|
# expired exactly grace days ago → last day of grace, warn without "within 0 days"
|
|
(
|
|
[
|
|
raw_entry(
|
|
added=(core.today_utc() - timedelta(days=27)).isoformat(),
|
|
expires=(core.today_utc() - timedelta(days=7)).isoformat(),
|
|
)
|
|
],
|
|
0,
|
|
"remove today — grace period ends",
|
|
),
|
|
# forward compat: a runner without an adapter (and an unknown field) warn but pass
|
|
([raw_entry(runner="some-future-runner", future_field="x")], 0, "no enforcement adapter"),
|
|
# a hand-edited future-dated entry must fail check, not sit active for years
|
|
(
|
|
[
|
|
raw_entry(
|
|
runner="playwright",
|
|
id="playwright/e2e/x.spec.ts",
|
|
added=(core.today_utc() + timedelta(days=365)).isoformat(),
|
|
expires=(core.today_utc() + timedelta(days=395)).isoformat(),
|
|
)
|
|
],
|
|
1,
|
|
"is in the future",
|
|
),
|
|
# jest and playwright have enforcement adapters; valid entries pass clean
|
|
([raw_entry(runner="jest", id="frontend/src/x.test.ts")], 0, "OK"),
|
|
([raw_entry(runner="playwright", id="playwright/e2e/login.spec.ts::Login redirects home")], 0, "OK"),
|
|
# known-product selector passes; unknown product fails
|
|
([raw_entry(id="product:batch-exports")], 0, "OK"),
|
|
([raw_entry(id="product:batch_exports")], 1, "dashed product name"),
|
|
],
|
|
)
|
|
def test_check(
|
|
runner: CliRunner, tmp_path: Path, entries: list[dict[str, Any]], expected_exit: int, expected_fragment: str
|
|
) -> None:
|
|
path = write_file(tmp_path / "q.json", entries)
|
|
result = cli(runner, path, "check")
|
|
assert result.exit_code == expected_exit, result.output
|
|
assert expected_fragment in result.output
|
|
|
|
|
|
def test_check_fails_on_malformed_file(runner: CliRunner, tmp_path: Path) -> None:
|
|
path = tmp_path / "q.json"
|
|
path.write_text('{"version": 99}')
|
|
result = cli(runner, path, "check")
|
|
assert result.exit_code == 1
|
|
assert "unsupported version" in result.output
|
|
|
|
|
|
def test_check_passes_on_missing_file(runner: CliRunner, tmp_path: Path) -> None:
|
|
result = cli(runner, tmp_path / "missing.json", "check")
|
|
assert result.exit_code == 0
|
|
|
|
|
|
def test_repo_quarantine_file_is_valid(runner: CliRunner) -> None:
|
|
assert core.QUARANTINE_PATH.name == ".test_quarantine.json"
|
|
result = runner.invoke(quarantine, ["check"])
|
|
assert result.exit_code == 0, result.output
|