From 3b3ddc6848f199b43f9a65d99de3c54605db21d5 Mon Sep 17 00:00:00 2001 From: AuraMindNest Date: Tue, 19 May 2026 15:58:42 -0600 Subject: [PATCH 1/6] Tests and Coverage --- .github/workflows/lint-and-format.yml | 43 +++++ .gitignore | 1 + README.md | 20 +- pyproject.toml | 22 +++ tests/endpoint/__init__.py | 3 + tests/endpoint/test_endpoint.py | 127 +++++++++++++ tests/formats/test_quickbook.py | 44 +++++ tests/test_boost_plugin_urls.py | 34 ---- tests/utils/test_quickbook.py | 255 ++++++++++++++++++++++++++ uv.lock | 112 ++++++++++- 10 files changed, 624 insertions(+), 37 deletions(-) create mode 100644 tests/endpoint/__init__.py create mode 100644 tests/endpoint/test_endpoint.py delete mode 100644 tests/test_boost_plugin_urls.py diff --git a/.github/workflows/lint-and-format.yml b/.github/workflows/lint-and-format.yml index 42184ba..8025c0c 100644 --- a/.github/workflows/lint-and-format.yml +++ b/.github/workflows/lint-and-format.yml @@ -10,6 +10,7 @@ on: permissions: contents: read + actions: write jobs: lint-and-format: @@ -45,3 +46,45 @@ jobs: env: RUFF_OUTPUT_FORMAT: github REUSE_OUTPUT_FORMAT: github + + test-coverage: + name: Tests and coverage + runs-on: ubuntu-latest + steps: + # actions/checkout v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + # actions/setup-python v6.2.0 + - name: Setup Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + with: + python-version: '3.14' + # astral-sh/setup-uv v8.1.0 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b + with: + version: 0.11.12 + - name: Install apt dependencies (Weblate venv) + run: sudo ./.github/ci/apt-install + - name: Install dependencies (incl. dev) + run: uv sync --frozen --group dev --group pre-commit + - name: Pytest with coverage + run: > + uv run --group dev --group pre-commit pytest -v --tb=short + --cov=boost_weblate + --cov-report=term-missing + --cov-report=xml:coverage.xml + --cov-report=html:htmlcov + --cov-fail-under=90 + - name: Coverage report (summary table) + run: uv run --group dev coverage report + # actions/upload-artifact v4.6.2 + - name: Upload coverage artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: coverage-${{ github.event.pull_request.number || github.run_id }} + path: | + coverage.xml + htmlcov/ + .coverage + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 81364c0..b378239 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ dist/ # Testing / coverage .pytest_cache/ .coverage +coverage.xml htmlcov/ .tox/ .nox/ diff --git a/README.md b/README.md index 9504626..1f1de7d 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,20 @@ Run the test suite: pytest ``` +Run with the same coverage gate as CI (terminal + XML + HTML, 90% minimum on `boost_weblate`): + +```bash +pytest -v --tb=short \ + --cov=boost_weblate \ + --cov-report=term-missing \ + --cov-report=xml:coverage.xml \ + --cov-report=html:htmlcov \ + --cov-fail-under=90 +coverage report +``` + +(`coverage.xml`, `htmlcov/`, and `.coverage` are gitignored; open `htmlcov/index.html` locally to browse line coverage.) + Run the same checks CI uses (lint, reuse, workflow lint, and pytest via [prek](https://pypi.org/project/prek/) reading `.pre-commit-config.yaml`): ```bash @@ -79,7 +93,7 @@ flowchart TB - **`src/boost_weblate/utils/`** — **Format-specific logic** with no Weblate import cycle: QuickBook parsing, segment extraction, translate-toolkit storage (`QuickBookFile` / `QuickBookUnit`), and reconstruction (`QuickBookTranslator`). New formats should add a sibling module (or package) here. -- **`tests/`** — **Pytest** layout mirrors `formats/` and `utils/` (`tests/formats/`, `tests/utils/`). Shared fixtures live under `tests/fixtures/`. `tests/conftest.py` configures `sys.path`, sets `DJANGO_SETTINGS_MODULE` to `tests.django_qbk_format_settings`, and calls `django.setup()` so format tests can load Weblate’s Django stack without requiring PostgreSQL. +- **`tests/`** — **Pytest** layout mirrors `src/boost_weblate/` (`tests/formats/`, `tests/utils/`, `tests/endpoint/`). Shared fixtures live under `tests/fixtures/`. `tests/conftest.py` configures `sys.path`, sets `DJANGO_SETTINGS_MODULE` to `tests.django_qbk_format_settings`, and calls `django.setup()` so format tests can load Weblate’s Django stack without requiring PostgreSQL. ## WEBLATE_FORMATS configuration @@ -107,7 +121,9 @@ That path is fixed; Weblate does not scan `DATA_DIR` for arbitrary override file - **Hooks:** use prek (or classic pre-commit) with `.pre-commit-config.yaml` so local runs match CI (Ruff, YAML/TOML checks, REUSE, actionlint, pytest). -- **Tests:** add tests next to the code you touch (`tests/formats/` or `tests/utils/`). Keep `django.setup()`-friendly patterns; heavy DB or migration suites are intentionally avoided in the bundled Django test settings. +- **Tests:** add tests next to the code you touch (`tests/formats/`, `tests/utils/`, or `tests/endpoint/`). Keep `django.setup()`-friendly patterns; heavy DB or migration suites are intentionally avoided in the bundled Django test settings. + +- **CI coverage:** the *Lint and format* workflow runs a **Tests and coverage** job that prints `term-missing` output, runs `coverage report`, writes `coverage.xml` and `htmlcov/`, and uploads those plus `.coverage` as a workflow artifact (download from the run’s *Artifacts* section on GitHub). Coverage is configured in `pyproject.toml` (`[tool.coverage.*]`); the job uses `uv sync --frozen --group dev --group pre-commit` so `pytest-cov` and `coverage[toml]` match the lockfile. - **Pull requests:** open PRs against the default branch on GitHub. Keep changes focused; ensure CI is green (build/wheel checks, lint, tests). Respond to review feedback on the PR thread; for design questions or bug reports, use [Issues](https://github.com/cppalliance/cppa-weblate-plugin/issues). diff --git a/pyproject.toml b/pyproject.toml index 85d0745..3b367ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,10 @@ build-backend = "uv_build" requires = ["uv_build>=0.8.18,<0.12.0"] [dependency-groups] +dev = [ + "coverage[toml]>=7.6.0", + "pytest-cov>=7.1.0" +] lint = [ {include-group = "pre-commit"} ] @@ -56,7 +60,9 @@ version = "0.1.0" [project.optional-dependencies] dev = [ + "coverage[toml]>=7.6.0", "prek==0.3.13", + "pytest-cov>=7.1.0", "pytest>=8.3" ] @@ -72,6 +78,20 @@ ignore = [ ".editorconfig" ] +[tool.coverage] + +[tool.coverage.report] +fail_under = 90 +omit = [ + "*/migrations/*", + "*/tests/*", + "*/__pycache__/*" +] +show_missing = true + +[tool.coverage.run] +source = ["boost_weblate"] + # liccheck: regex on PyPI license classifiers (as_regex). [tool.liccheck] as_regex = true @@ -103,6 +123,8 @@ level = "cautious" unauthorized_licenses = [] [tool.pytest.ini_options] +python_classes = ["Test*"] +python_files = ["test_*.py", "*_test.py"] pythonpath = ["src", "."] testpaths = ["tests"] diff --git a/tests/endpoint/__init__.py b/tests/endpoint/__init__.py new file mode 100644 index 0000000..62d857b --- /dev/null +++ b/tests/endpoint/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2026 Andrew Zhang +# +# SPDX-License-Identifier: BSL-1.0 diff --git a/tests/endpoint/test_endpoint.py b/tests/endpoint/test_endpoint.py new file mode 100644 index 0000000..bde35c6 --- /dev/null +++ b/tests/endpoint/test_endpoint.py @@ -0,0 +1,127 @@ +# SPDX-FileCopyrightText: 2026 Andrew Zhang +# +# SPDX-License-Identifier: BSL-1.0 + +from __future__ import annotations + +import builtins +import importlib.util +import logging +import sys +import types +from pathlib import Path + +import pytest +from django.test import RequestFactory + +from boost_weblate.endpoint.views import plugin_ping + +_REPO_ROOT = Path(__file__).resolve().parents[2] + + +def test_register_plugin_urls_appends_once(monkeypatch: pytest.MonkeyPatch) -> None: + fake = types.ModuleType("weblate.urls") + fake.real_patterns = [] + fake._cppa_boost_weblate_urls_registered = False + monkeypatch.setitem(sys.modules, "weblate.urls", fake) + + from boost_weblate.endpoint.apps import register_plugin_urls + + register_plugin_urls() + register_plugin_urls() + + assert len(fake.real_patterns) == 1 + + +def test_plugin_ping_returns_200() -> None: + request = RequestFactory().get("/plugin-ping/") + response = plugin_ping(request) + assert response.status_code == 200 + assert response.content == b"ok" + + +def test_register_plugin_urls_skips_on_weblate_urls_import_error( + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, +) -> None: + real_import = builtins.__import__ + + def fake_import( + name: str, + globals_arg: dict | None = None, + locals_arg: dict | None = None, + fromlist: tuple[str, ...] = (), + level: int = 0, + ): + if name == "weblate.urls": + msg = "No module named 'weblate.urls'" + raise ModuleNotFoundError(msg) + return real_import(name, globals_arg, locals_arg, fromlist, level) + + monkeypatch.setattr(builtins, "__import__", fake_import) + caplog.set_level(logging.DEBUG, logger="boost_weblate.endpoint.apps") + + from boost_weblate.endpoint.apps import register_plugin_urls + + register_plugin_urls() + assert "skipping URL registration" in caplog.text + + +def test_register_plugin_urls_warns_without_real_patterns( + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, +) -> None: + fake = types.ModuleType("weblate.urls") + fake._cppa_boost_weblate_urls_registered = False + monkeypatch.setitem(sys.modules, "weblate.urls", fake) + + from boost_weblate.endpoint.apps import register_plugin_urls + + with caplog.at_level(logging.WARNING, logger="boost_weblate.endpoint.apps"): + register_plugin_urls() + assert "no real_patterns" in caplog.text + + +def test_boost_endpoint_config_ready_registers_urls( + monkeypatch: pytest.MonkeyPatch, +) -> None: + fake = types.ModuleType("weblate.urls") + fake.real_patterns = [] + fake._cppa_boost_weblate_urls_registered = False + monkeypatch.setitem(sys.modules, "weblate.urls", fake) + + import boost_weblate.endpoint.apps as apps_module + from boost_weblate.endpoint.apps import BoostEndpointConfig + + cfg = BoostEndpointConfig("boost_weblate.endpoint", apps_module) + cfg.ready() + assert len(fake.real_patterns) == 1 + + +def test_settings_override_exec_appends_format_and_endpoint_app() -> None: + """Load ``settings_override.py`` as ``boost_weblate.settings_override``. + + Used so coverage attributes execution to the real file path. + """ + settings_path = _REPO_ROOT / "src" / "boost_weblate" / "settings_override.py" + name = "boost_weblate.settings_override" + saved = sys.modules.pop(name, None) + try: + spec = importlib.util.spec_from_file_location(name, settings_path) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + module.__dict__["WEBLATE_FORMATS"] = () + module.__dict__["INSTALLED_APPS"] = () + sys.modules[name] = module + spec.loader.exec_module(module) + formats = module.WEBLATE_FORMATS + apps_tuple = module.INSTALLED_APPS + finally: + if saved is not None: + sys.modules[name] = saved + else: + sys.modules.pop(name, None) + + assert isinstance(formats, tuple) + assert "boost_weblate.formats.quickbook.QuickBookFormat" in formats + assert "boost_weblate.endpoint.apps.BoostEndpointConfig" in apps_tuple diff --git a/tests/formats/test_quickbook.py b/tests/formats/test_quickbook.py index 910c52f..49b12d8 100644 --- a/tests/formats/test_quickbook.py +++ b/tests/formats/test_quickbook.py @@ -208,6 +208,46 @@ def test_existing_units_merge_orangutan(tmp_path: Path) -> None: assert testfile.read_text(encoding="utf-8") == _EXPECTED_AFTER_EXISTING_UNITS +def test_convertfile_merge_duplicates_uses_merge_style(tmp_path: Path) -> None: + """``merge_duplicates`` selects ``duplicate_style="merge"`` in ``convertfile``.""" + from boost_weblate.formats.quickbook import QuickBookFormat + + template_path = tmp_path / "tpl_merge.qbk" + translation_path = tmp_path / "tr_merge.qbk" + template_path.write_text("[h2 One]\n", encoding="utf-8") + translation_path.write_text("[h2 Jedna]\n", encoding="utf-8") + + storage = QuickBookFormat( + str(translation_path), + template_store=QuickBookFormat(str(template_path), is_template=True), + file_format_params={"merge_duplicates": True}, + ) + assert len(storage.content_units) == 1 + assert storage.content_units[0].source == "One" + + +def test_save_content_resolves_template_path_via_storefile_name( + tmp_path: Path, +) -> None: + """Template ``storefile`` may be a binary handle; ``save`` uses ``.name``.""" + from boost_weblate.formats.quickbook import QuickBookFormat + + template_path = tmp_path / "tpl_handle.qbk" + translation_path = tmp_path / "tr_handle.qbk" + template_path.write_text("[h2 Hello]\n", encoding="utf-8") + translation_path.write_text("[h2 Ahoj]\n", encoding="utf-8") + + with open(template_path, "rb") as template_file: + template_fmt = QuickBookFormat(template_file, is_template=True) + storage = QuickBookFormat( + str(translation_path), + template_store=template_fmt, + ) + storage.save() + + assert translation_path.read_text(encoding="utf-8") == "[h2 Ahoj]\n" + + def main(argv: list[str]) -> int: _bootstrap_django() @@ -233,6 +273,10 @@ def main(argv: list[str]) -> int: print("import existing (cs/cs2): OK") test_existing_units_merge_orangutan(p) print("existing_units merge: OK") + test_convertfile_merge_duplicates_uses_merge_style(p) + print("merge_duplicates convertfile: OK") + test_save_content_resolves_template_path_via_storefile_name(p) + print("save_content template storefile.name: OK") return 0 diff --git a/tests/test_boost_plugin_urls.py b/tests/test_boost_plugin_urls.py deleted file mode 100644 index 5a3366b..0000000 --- a/tests/test_boost_plugin_urls.py +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Andrew Zhang -# -# SPDX-License-Identifier: BSL-1.0 - -from __future__ import annotations - -import sys -import types - -import pytest -from django.test import RequestFactory - -from boost_weblate.endpoint.views import plugin_ping - - -def test_register_plugin_urls_appends_once(monkeypatch: pytest.MonkeyPatch) -> None: - fake = types.ModuleType("weblate.urls") - fake.real_patterns = [] - fake._cppa_boost_weblate_urls_registered = False - monkeypatch.setitem(sys.modules, "weblate.urls", fake) - - from boost_weblate.endpoint.apps import register_plugin_urls - - register_plugin_urls() - register_plugin_urls() - - assert len(fake.real_patterns) == 1 - - -def test_plugin_ping_returns_200() -> None: - request = RequestFactory().get("/plugin-ping/") - response = plugin_ping(request) - assert response.status_code == 200 - assert response.content == b"ok" diff --git a/tests/utils/test_quickbook.py b/tests/utils/test_quickbook.py index 80d0815..bb60543 100644 --- a/tests/utils/test_quickbook.py +++ b/tests/utils/test_quickbook.py @@ -26,10 +26,17 @@ from translate.storage.pypo import pofile # noqa: E402 +from boost_weblate.utils import quickbook as quickbook_mod # noqa: E402 from boost_weblate.utils.quickbook import ( # noqa: E402 QuickBookFile, QuickBookTranslator, + QuickBookUnit, + _find_bracket_end, + _has_prose, + _parse_bracket_keyword, _parse_qbk, + _parse_table_inner, + _Seg, ) DEFAULT_FIXTURE = _REPO_ROOT / "tests/fixtures/quickbook_fixture.qbk" @@ -110,6 +117,254 @@ def close(self) -> None: assert marker not in result +def test_find_bracket_end_triple_quote_and_escape() -> None: + s = r"[keyword '''still [nested] here''' tail]" + start = s.index("[") + end = _find_bracket_end(s, start) + assert end == len(s) - 1 + esc = r"[show \[literal\] brackets]" + start_e = esc.index("[") + assert _find_bracket_end(esc, start_e) == len(esc) - 1 + + +def test_find_bracket_end_unclosed() -> None: + s = "[never closes" + assert _find_bracket_end(s, 0) == -1 + + +def test_parse_bracket_keyword_whitespace_after_sigil() -> None: + kw, off = _parse_bracket_keyword("[@ trailing]") + assert kw == "@" + assert off == 5 + + +def test_has_prose_plain_and_macro_only() -> None: + assert _has_prose("plain words") is True + assert _has_prose("__only_macro__") is False + assert _has_prose("__a__, __b__.") is False + assert _has_prose("text __x__ more") is True + + +def test_has_prose_empty_after_bracket_stripping() -> None: + """Bracket pairs collapse to spaces; bare text can become empty after strip.""" + assert _has_prose("[]") is False + + +def test_parse_table_inner_title_only_single_line() -> None: + body = "Single line title" + segs = _parse_table_inner(body, 0, len(body), 1, "table", 0) + assert len(segs) == 1 + assert segs[0].seg_type == "table-title" + + +def test_parse_table_inner_skips_bare_line_and_parses_row() -> None: + body = "T\nnot a row token\n[[a][b]]" + segs = _parse_table_inner(body, 0, len(body), 1, "table", 0) + titles = [s for s in segs if s.seg_type == "table-title"] + cells = [s for s in segs if s.seg_type == "table"] + assert titles and cells + assert cells[0].msgid == "a" + + +def test_parse_table_inner_malformed_row_and_cell_brackets() -> None: + content = "T\n[[a][b]\n[row [no close]\n[[c][d]]" + segs = _parse_table_inner(content, 0, len(content), 1, "table", 0) + assert any(s.msgid == "c" for s in segs) + + +def test_parse_qbk_section_recursion_depth_cap() -> None: + inner = "deepest body text" + for d in range(10, -1, -1): + inner = f"[section:id{d} T{d}\n{inner}\n]" + segs = _parse_qbk(inner) + assert not any("deepest" in s.msgid for s in segs) + + +def test_parse_qbk_trailing_indented_spaces_without_final_newline() -> None: + segs = _parse_qbk("alpha\n ") + assert any(s.msgid == "alpha" for s in segs) + + +def test_parse_qbk_leading_triple_quote_block() -> None: + segs = _parse_qbk("'''raw escape'''\n[h2 heading]\n") + assert any(s.seg_type == "heading" for s in segs) + + +def test_parse_qbk_unclosed_bracket_line_skipped_in_block() -> None: + segs = _parse_qbk("[h2 ok]\n[broken\nstill junk\n") + assert any(s.msgid == "ok" for s in segs) + + +def test_parse_qbk_empty_section_body_skipped() -> None: + assert _parse_qbk("[section\n\n]") == [] + + +def test_parse_qbk_multiline_section_title_and_nested_body() -> None: + text = "[section:sec1 Title on first line\nBody paragraph in nested scope.\n]\n" + segs = _parse_qbk(text) + kinds = {s.seg_type for s in segs} + assert "section-title" in kinds + assert "paragraph" in kinds + + +def test_parse_qbk_multiline_blockquote() -> None: + text = "[:first line\nsecond line in blockquote\n]" + segs = _parse_qbk(text) + assert len(segs) == 1 + assert "first line" in segs[0].msgid and "second line" in segs[0].msgid + + +def test_parse_qbk_paragraph_stops_at_triple_quote_line() -> None: + segs = _parse_qbk("line one\n'''starts raw\n") + assert len(segs) == 1 + assert segs[0].msgid == "line one" + + +def test_parse_qbk_paragraph_line_with_unclosed_bracket_keyword() -> None: + text = "before [incomplete\nstill before\n[h2 after]\n" + segs = _parse_qbk(text) + joined = " ".join(s.msgid for s in segs if s.seg_type == "paragraph") + assert "before" in joined and "incomplete" in joined and "still before" in joined + + +def test_quickbook_unit_notes_and_getid() -> None: + u = QuickBookUnit("src-id") + u.setdocpath("qbk:3") + assert u.getid() == "qbk:3" + u.setdocpath("") + assert u.getid() == "src-id" + u.addnote("one") + u.addnote("two") + assert u.getnotes() == "one\ntwo" + + +def test_quickbook_file_skips_empty_msgid_segments( + monkeypatch: pytest.MonkeyPatch, +) -> None: + real = quickbook_mod._parse_qbk + + def combined(txt: str, *a, **k): + out = list(real(txt, *a, **k)) + out.append(_Seg(0, 1, 1, "paragraph", "", False, "paragraph")) + return out + + monkeypatch.setattr(quickbook_mod, "_parse_qbk", combined) + + class _F: + name = "x.qbk" + + def read(self) -> bytes: + return b"[h2 only]\n" + + def close(self) -> None: + pass + + store = QuickBookFile(inputfile=_F()) + assert len(store.units) == 1 + + +def test_translator_respects_should_output_store( + monkeypatch: pytest.MonkeyPatch, path: Path +) -> None: + monkeypatch.setattr( + "translate.convert.convert.should_output_store", lambda *_a, **_k: False + ) + po = pofile() + translator = QuickBookTranslator( + inputstore=po, includefuzzy=True, outputthreshold=0.5 + ) + out = BytesIO() + + class _F: + name = str(path) + + def read(self) -> bytes: + return path.read_bytes() + + def close(self) -> None: + pass + + assert translator.translate(_F(), out) is False + assert out.getvalue() == b"" + + +def test_translator_lookup_untranslated_uses_source() -> None: + qbk = "[h2 QB_UNIQUE_MSGID]\n" + po = pofile() + po.addsourceunit("QB_UNIQUE_MSGID") + translator = QuickBookTranslator( + inputstore=po, includefuzzy=True, outputthreshold=None + ) + out = BytesIO() + + class _F: + name = "t.qbk" + + def read(self) -> bytes: + return qbk.encode() + + def close(self) -> None: + pass + + assert translator.translate(_F(), out) == 1 + assert b"QB_UNIQUE_MSGID" in out.getvalue() + + +def test_translator_lookup_fuzzy_target_used_when_includefuzzy() -> None: + qbk = "[h2 FUZZY_HEAD]\n" + po = pofile() + u = po.addsourceunit("FUZZY_HEAD") + u.target = "Titre flou" + u.markfuzzy(True) + translator = QuickBookTranslator( + inputstore=po, includefuzzy=True, outputthreshold=None + ) + out = BytesIO() + + class _F: + name = "t.qbk" + + def read(self) -> bytes: + return qbk.encode() + + def close(self) -> None: + pass + + assert translator.translate(_F(), out) == 1 + assert b"Titre flou" in out.getvalue() + + +def test_parse_qbk_paragraph_breaks_on_soft_wrap_space_line() -> None: + segs = _parse_qbk("first line\n second line looks wrapped") + assert len(segs) == 1 + assert segs[0].msgid == "first line" + + +def test_parse_qbk_paragraph_unclosed_bracket_then_para_break() -> None: + text = "intro\n[note not closed here\n[h2 real]\n" + segs = _parse_qbk(text) + assert any(s.seg_type == "heading" and "real" in s.msgid for s in segs) + + +def test_quickbook_unit_getlocations_roundtrip() -> None: + u = QuickBookUnit("src") + assert u.getlocations() == [] + u.addlocation("fixture.qbk:12") + assert u.getlocations() == ["fixture.qbk:12"] + + +def test_parse_qbk_unrecognized_bracket_command_skipped() -> None: + segs = _parse_qbk("[zzzmacro body text]\nPlain after.\n") + assert not any(s.msgid == "body text" for s in segs) + assert any("Plain after" in s.msgid for s in segs) + + +def test_parse_table_inner_cell_bracket_extends_past_row() -> None: + body = "T\n[[a][b [orphan]\n[[c][d]]" + segs = _parse_table_inner(body, 0, len(body), 1, "table", 0) + assert any(s.msgid == "c" for s in segs) + + def main(argv: list[str]) -> int: path = Path(argv[1]).resolve() if len(argv) > 1 else DEFAULT_FIXTURE if not path.is_file(): diff --git a/uv.lock b/uv.lock index eed45e4..4416a20 100644 --- a/uv.lock +++ b/uv.lock @@ -565,6 +565,90 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/b8/40/c199d095151addf69efdb4b9ca3a4f20f70e20508d6222bffb9b76f58573/constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9", size = 13547, upload-time = "2023-10-28T23:18:23.038Z"} ] +[[package]] +name = "coverage" +sdist = {url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z"} +source = {registry = "https://pypi.org/simple"} +version = "7.14.0" +wheels = [ + {url = "https://files.pythonhosted.org/packages/09/1e/2f996b2c8415cbb6f54b0f5ec1ee850c96d7911961afb4fc05f4a89d8c58/coverage-7.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5", size = 219967, upload-time = "2026-05-10T18:00:13.756Z"}, + {url = "https://files.pythonhosted.org/packages/34/23/35c7aea1274aef7525bdd2dc92f710bdde6d11652239d71d1ec450067939/coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662", size = 220329, upload-time = "2026-05-10T18:00:15.264Z"}, + {url = "https://files.pythonhosted.org/packages/75/cf/a8f4b43a16e194b0261257ad28ded5853ec052570afef4a84e1d81189f3b/coverage-7.14.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f", size = 251839, upload-time = "2026-05-10T18:00:17.16Z"}, + {url = "https://files.pythonhosted.org/packages/69/ff/6699e7b71e60d3049eb2bdcbc95ee3f35707b2b0e48f32e9e63d3ce30c08/coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67", size = 254576, upload-time = "2026-05-10T18:00:18.829Z"}, + {url = "https://files.pythonhosted.org/packages/22/ec/c936d495fcd67f48f03a9c4ad3297ff80d1f222a5df3980f15b34c186c21/coverage-7.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9", size = 255690, upload-time = "2026-05-10T18:00:20.648Z"}, + {url = "https://files.pythonhosted.org/packages/5c/42/5af63f636cc62a4a2b1b3ba9146f6ee6f53a35a50d5cefc54d5670f60999/coverage-7.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb", size = 257949, upload-time = "2026-05-10T18:00:22.28Z"}, + {url = "https://files.pythonhosted.org/packages/26/d3/a225317bd2012132a27e1176d51660b826f99bb975876463c44ea0d7ee5a/coverage-7.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e", size = 252242, upload-time = "2026-05-10T18:00:24.076Z"}, + {url = "https://files.pythonhosted.org/packages/f1/7f/9e65495298c3ea414742998539c37d048b5e81cc818fb1828cc6b51d10bf/coverage-7.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3", size = 253608, upload-time = "2026-05-10T18:00:25.588Z"}, + {url = "https://files.pythonhosted.org/packages/94/46/1522b524a35bdad22b2b8c4f9d32d0a104b524726ec380b2db68db1746f5/coverage-7.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4", size = 251753, upload-time = "2026-05-10T18:00:27.104Z"}, + {url = "https://files.pythonhosted.org/packages/f3/e9/cdf00d38817742c541ade405e115a3f7bf36e6f2a8b99d4f209861b85a2d/coverage-7.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1", size = 255823, upload-time = "2026-05-10T18:00:29.038Z"}, + {url = "https://files.pythonhosted.org/packages/38/fc/5e7877cf5f902d08a17ff1c532511476d87e1bea355bd5028cb97f902e79/coverage-7.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5", size = 251323, upload-time = "2026-05-10T18:00:30.647Z"}, + {url = "https://files.pythonhosted.org/packages/18/9d/50f05a72dff8487464fdd4178dda5daed642a060e60afb644e3d45123559/coverage-7.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595", size = 253197, upload-time = "2026-05-10T18:00:32.211Z"}, + {url = "https://files.pythonhosted.org/packages/00/3f/6f61ffe6439df266c3cf60f5c99cfaa21103d0210d706a42fc6c30683ff8/coverage-7.14.0-cp312-cp312-win32.whl", hash = "sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27", size = 222515, upload-time = "2026-05-10T18:00:33.717Z"}, + {url = "https://files.pythonhosted.org/packages/85/19/93853133df2cb371083285ef6a93982a0173e7a233b0f61373ba9fd30eb2/coverage-7.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2", size = 223324, upload-time = "2026-05-10T18:00:35.172Z"}, + {url = "https://files.pythonhosted.org/packages/74/18/9f7fe62f659f24b7a82a0be56bf94c1bd0a89e0ae7ab4c668f6e82404294/coverage-7.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d", size = 221944, upload-time = "2026-05-10T18:00:37.014Z"}, + {url = "https://files.pythonhosted.org/packages/6b/76/b7c66ee3c66e1b0f9d894c8125983aa0c03fb2336f2fd16559f9c966157f/coverage-7.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef", size = 219990, upload-time = "2026-05-10T18:00:38.887Z"}, + {url = "https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66", size = 220365, upload-time = "2026-05-10T18:00:40.864Z"}, + {url = "https://files.pythonhosted.org/packages/44/6f/9ad575d505b4d805b254febc8a5b338a2efe278f8786e56ff1cb8413f9c3/coverage-7.14.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b", size = 251363, upload-time = "2026-05-10T18:00:42.489Z"}, + {url = "https://files.pythonhosted.org/packages/6f/5f/b5370068b2f57787454592ed7dcd1002f0f1703b7db1fa30f6a325a4ca6e/coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca", size = 253961, upload-time = "2026-05-10T18:00:44.079Z"}, + {url = "https://files.pythonhosted.org/packages/29/1e/51adf17738976e8f2b85ddef7b7aa12a0838b056c92f175941d8862767c1/coverage-7.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7", size = 255193, upload-time = "2026-05-10T18:00:45.623Z"}, + {url = "https://files.pythonhosted.org/packages/9e/7b/5bfd7ac1df3b881c2ac7a5cbc99c7609e6296c402f5ef587cd81c6f355b3/coverage-7.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2", size = 257326, upload-time = "2026-05-10T18:00:47.173Z"}, + {url = "https://files.pythonhosted.org/packages/7d/38/1d37d316b174fad3843a1d76dbdfe4398771c9ecd0515935dd9ece9cd627/coverage-7.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367", size = 251582, upload-time = "2026-05-10T18:00:49.152Z"}, + {url = "https://files.pythonhosted.org/packages/34/46/746704f95980ba220214e1a41e18cec5aea80a898eaa53c51bf2d645ff36/coverage-7.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9", size = 253325, upload-time = "2026-05-10T18:00:51.252Z"}, + {url = "https://files.pythonhosted.org/packages/e1/b9/bbe87206d9687b192352f893797825b5f5b15ecd3aa9c68fbff0c074d77b/coverage-7.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087", size = 251291, upload-time = "2026-05-10T18:00:52.816Z"}, + {url = "https://files.pythonhosted.org/packages/46/57/b8cdb12ac0d73ef0243218bd5e22c9df8f92edab8018213a86aec67c5324/coverage-7.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef", size = 255448, upload-time = "2026-05-10T18:00:54.548Z"}, + {url = "https://files.pythonhosted.org/packages/1f/d4/5002019538b2036ce3c84340f54d2fd5100d55b0a6b0894eee56128d03c7/coverage-7.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52", size = 251110, upload-time = "2026-05-10T18:00:56.122Z"}, + {url = "https://files.pythonhosted.org/packages/37/53/20c5009477660f084e6ed60bc02a91894b8e234e617e86ecfd9aaf78e27b/coverage-7.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe", size = 252885, upload-time = "2026-05-10T18:00:57.967Z"}, + {url = "https://files.pythonhosted.org/packages/ae/ab/3cf6427ac9c1f1db747dbb1ce71dde47984876d4c2cfd018a3fef0a78d4d/coverage-7.14.0-cp313-cp313-win32.whl", hash = "sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae", size = 222539, upload-time = "2026-05-10T18:00:59.581Z"}, + {url = "https://files.pythonhosted.org/packages/8f/b8/9228523e80321c2cb4880d1f589bc0171f2f71432c35118ad04dc01decce/coverage-7.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e", size = 223344, upload-time = "2026-05-10T18:01:01.531Z"}, + {url = "https://files.pythonhosted.org/packages/a3/99/118daa192f95e3a6cb2740100fbf8797cda1734b4134ef0b5d501a7fa8f3/coverage-7.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96", size = 221966, upload-time = "2026-05-10T18:01:03.16Z"}, + {url = "https://files.pythonhosted.org/packages/e6/f1/a46cc0c013be170216253184a32366d7cbdb9252feaec866b05c2d12a894/coverage-7.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90", size = 220679, upload-time = "2026-05-10T18:01:05.058Z"}, + {url = "https://files.pythonhosted.org/packages/64/8c/9c30a3d311a34177fa432995be7fbfc64477d8bac5630bd38055b1c9b424/coverage-7.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1", size = 221033, upload-time = "2026-05-10T18:01:07.002Z"}, + {url = "https://files.pythonhosted.org/packages/9a/cd/3fb5e06c3badefd0c1b47e2044fdca67f8220a4ec2e7fcfb476aa0a67c6c/coverage-7.14.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd", size = 262333, upload-time = "2026-05-10T18:01:08.903Z"}, + {url = "https://files.pythonhosted.org/packages/a8/e6/fbc322325c7294d3e22c1ad6b79e45d0806b25228c8e5842aed6d8169aa7/coverage-7.14.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc", size = 264410, upload-time = "2026-05-10T18:01:10.531Z"}, + {url = "https://files.pythonhosted.org/packages/08/92/c497b264bec1673c47cc77e26f760fcda4654cabf1f39546d1a23a3b8c35/coverage-7.14.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426", size = 266836, upload-time = "2026-05-10T18:01:12.19Z"}, + {url = "https://files.pythonhosted.org/packages/78/fc/045da320987f401af5d2815d351e8aa799aec859f60e29f445e3089eeedb/coverage-7.14.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899", size = 267974, upload-time = "2026-05-10T18:01:13.926Z"}, + {url = "https://files.pythonhosted.org/packages/1b/ae/227b1e379497fb7a4fc3286e620f80c8a1e7cec66d45695a01639eb1af65/coverage-7.14.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b", size = 261578, upload-time = "2026-05-10T18:01:15.564Z"}, + {url = "https://files.pythonhosted.org/packages/a0/f5/3570342900f2acea31d33ff1590c5d8bac1a8e1a2e1c6d34a5d5e61de681/coverage-7.14.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90", size = 264394, upload-time = "2026-05-10T18:01:17.607Z"}, + {url = "https://files.pythonhosted.org/packages/16/29/de1bbc01c935b28f89b1dc3db85b011c055e843a8e5e3b83141c3f80af7f/coverage-7.14.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f", size = 262022, upload-time = "2026-05-10T18:01:19.304Z"}, + {url = "https://files.pythonhosted.org/packages/35/95/f53890b0bf2fc10ab168e05d38869215e73ca24c4cb521c3bb0eb62fe16b/coverage-7.14.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d", size = 265732, upload-time = "2026-05-10T18:01:21.494Z"}, + {url = "https://files.pythonhosted.org/packages/ed/ea/c919e259081dd2bdf0e43b87209709ba7ec2e4117c2a7f5185379c43463c/coverage-7.14.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47", size = 260921, upload-time = "2026-05-10T18:01:23.533Z"}, + {url = "https://files.pythonhosted.org/packages/1a/2c/c2831889705a81dc5d1c6ca12e4d8e9b95dfc146d153488a6c0ea685d28e/coverage-7.14.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477", size = 263109, upload-time = "2026-05-10T18:01:25.165Z"}, + {url = "https://files.pythonhosted.org/packages/5a/a9/2fcae5003cac3d63fe344d2166243c2756935f48420863c5272b240d550b/coverage-7.14.0-cp313-cp313t-win32.whl", hash = "sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab", size = 223212, upload-time = "2026-05-10T18:01:27.157Z"}, + {url = "https://files.pythonhosted.org/packages/3f/bb/18e94d7b14b9b398164197114a587a04ab7c9fdbe1d237eef57311c5e883/coverage-7.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917", size = 224272, upload-time = "2026-05-10T18:01:29.107Z"}, + {url = "https://files.pythonhosted.org/packages/db/56/4f14fad782b035c81c4ffd09159e7103d42bb1d93ac8496d04b90a11b7da/coverage-7.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8", size = 222530, upload-time = "2026-05-10T18:01:31.151Z"}, + {url = "https://files.pythonhosted.org/packages/1c/18/b9a6586d73992807c26f9a5f274131be3d76b56b18a82b9392e2a25d2e45/coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", size = 220036, upload-time = "2026-05-10T18:01:33.057Z"}, + {url = "https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", size = 220368, upload-time = "2026-05-10T18:01:34.705Z"}, + {url = "https://files.pythonhosted.org/packages/69/aa/c12e52a5ba148d9995229d557e3be6e554fe469addc0e9241b2f0956d8ea/coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", size = 251417, upload-time = "2026-05-10T18:01:36.949Z"}, + {url = "https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", size = 253924, upload-time = "2026-05-10T18:01:38.985Z"}, + {url = "https://files.pythonhosted.org/packages/33/c4/59c3de0bd1b538824173fd518fed51c1ce740ca5ed68e74545983f4053a9/coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", size = 255269, upload-time = "2026-05-10T18:01:40.957Z"}, + {url = "https://files.pythonhosted.org/packages/7b/a9/36dfa153a62040296f6e7febfdb20a5720622f6ef5a81a41e8237b9a5344/coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", size = 257583, upload-time = "2026-05-10T18:01:42.607Z"}, + {url = "https://files.pythonhosted.org/packages/26/7b/cc2c048d4114d9ab1c2409e9ee365e5ae10736df6dffcfc9444effa6c708/coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", size = 251434, upload-time = "2026-05-10T18:01:44.537Z"}, + {url = "https://files.pythonhosted.org/packages/ee/df/6770eaa576e604575e9a78055313250faef5faa84bd6f71a39fece519c43/coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", size = 253280, upload-time = "2026-05-10T18:01:46.175Z"}, + {url = "https://files.pythonhosted.org/packages/ad/9e/1c0264514a3f98259a6d64765a397b2c8373e3ba59ee722a4802d3ec0c61/coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", size = 251241, upload-time = "2026-05-10T18:01:48.732Z"}, + {url = "https://files.pythonhosted.org/packages/64/16/4efdf3e3c4079cdbf0ece56a2fea872df9e8a3e15a13a0af4400e1075944/coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", size = 255516, upload-time = "2026-05-10T18:01:50.819Z"}, + {url = "https://files.pythonhosted.org/packages/93/69/b1de96346603881b3d1bc8d6447c83200e1c9700ffbaff926ba01ff5724c/coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", size = 251059, upload-time = "2026-05-10T18:01:52.773Z"}, + {url = "https://files.pythonhosted.org/packages/a4/66/2881853e0363a5e0a724d1103e53650795367471b6afb234f8b49e713bc6/coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", size = 252716, upload-time = "2026-05-10T18:01:54.506Z"}, + {url = "https://files.pythonhosted.org/packages/55/5c/0d3305d002c41dcde873dbe456491e663dc55152ca526b630b5c47efd62f/coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", size = 222788, upload-time = "2026-05-10T18:01:56.487Z"}, + {url = "https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", size = 223600, upload-time = "2026-05-10T18:01:58.497Z"}, + {url = "https://files.pythonhosted.org/packages/00/70/a18c408e674bc26281cadaedc7351f929bd2094e191e4b15271c30b084cc/coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", size = 222168, upload-time = "2026-05-10T18:02:00.411Z"}, + {url = "https://files.pythonhosted.org/packages/3d/89/2681f071d238b62aff8dfc2ab44fc24cfdb38d1c01f391a80522ff5d3a16/coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", size = 220766, upload-time = "2026-05-10T18:02:02.313Z"}, + {url = "https://files.pythonhosted.org/packages/bd/c7/c987babafd9207ffa1995e1ef1f9b26762cf4963aa768a66b6f0501e4616/coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", size = 221035, upload-time = "2026-05-10T18:02:04.017Z"}, + {url = "https://files.pythonhosted.org/packages/5a/e9/d6a5ac3b333088143d6fc877d398a9a674dc03124a2f776e131f03864823/coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", size = 262405, upload-time = "2026-05-10T18:02:05.915Z"}, + {url = "https://files.pythonhosted.org/packages/38/b1/e70838d29a7c08e22d44398a46db90815bbcbf28de06992bd9210d1a8d8e/coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", size = 264530, upload-time = "2026-05-10T18:02:07.582Z"}, + {url = "https://files.pythonhosted.org/packages/6b/73/5c31ef97763288d03d9995152b96d5475b527c63d91c84b01caea894b83a/coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", size = 266932, upload-time = "2026-05-10T18:02:09.401Z"}, + {url = "https://files.pythonhosted.org/packages/e1/76/dd56d80f29c5f05b4d76f7e7c6d47cafacae017189c75c5759d24f9ff0cc/coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", size = 268062, upload-time = "2026-05-10T18:02:11.399Z"}, + {url = "https://files.pythonhosted.org/packages/6e/c7/27ba85cd5b95614f159ff93ebff1901584a8d192e2e5e24c4943a7453f59/coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", size = 261504, upload-time = "2026-05-10T18:02:13.257Z"}, + {url = "https://files.pythonhosted.org/packages/13/2e/e8149f60ab5d5684c6eee881bdf34b127115cddbb958b196768dd9d63473/coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", size = 264398, upload-time = "2026-05-10T18:02:15.063Z"}, + {url = "https://files.pythonhosted.org/packages/d9/7f/1261b025285323225f4b4abffa5a643649dfd67e25ddca7ebcbdea3b7cb3/coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", size = 262000, upload-time = "2026-05-10T18:02:16.756Z"}, + {url = "https://files.pythonhosted.org/packages/d3/dc/829c54f60b9d08389439c00f813c752781c496fc5788c78d8006db4b4f2b/coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", size = 265732, upload-time = "2026-05-10T18:02:18.817Z"}, + {url = "https://files.pythonhosted.org/packages/ed/b0/70bd1419941652fa062689cba9c3eeafb8f5e6fbb890bce41c3bdda5dbd6/coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", size = 260847, upload-time = "2026-05-10T18:02:20.528Z"}, + {url = "https://files.pythonhosted.org/packages/f2/73/be40b2390656c654d35ea0015ea7ba3d945769cf80790ad5e0bb2d56d2ba/coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", size = 263166, upload-time = "2026-05-10T18:02:22.337Z"}, + {url = "https://files.pythonhosted.org/packages/29/55/4a643f712fcf7cf2881f8ec1e0ccb7b164aff3108f69b51801246c8799f2/coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", size = 223573, upload-time = "2026-05-10T18:02:24.11Z"}, + {url = "https://files.pythonhosted.org/packages/27/96/3acae5da0953be042c0b4dea6d6789d2f080701c77b88e44d5bd41b9219b/coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", size = 224680, upload-time = "2026-05-10T18:02:25.896Z"}, + {url = "https://files.pythonhosted.org/packages/93/3d/6ab5d2dd8325d838737c6f8d83d62eb6230e0d70b87b51b57bbfd08fa767/coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", size = 222703, upload-time = "2026-05-10T18:02:27.822Z"}, + {url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z"} +] + [[package]] dependencies = [ {name = "weblate"} @@ -574,6 +658,10 @@ source = {editable = "."} version = "0.1.0" [package.dev-dependencies] +dev = [ + {name = "coverage"}, + {name = "pytest-cov"} +] lint = [ {name = "prek"}, {name = "pytest"} @@ -590,12 +678,18 @@ tooling = [ [package.metadata] provides-extras = ["dev"] requires-dist = [ + {name = "coverage", extras = ["toml"], marker = "extra == 'dev'", specifier = ">=7.6.0"}, {name = "prek", marker = "extra == 'dev'", specifier = "==0.3.13"}, {name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3"}, + {name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.1.0"}, {name = "weblate", specifier = ">=5.16.0"} ] [package.metadata.requires-dev] +dev = [ + {name = "coverage", extras = ["toml"], specifier = ">=7.6.0"}, + {name = "pytest-cov", specifier = ">=7.1.0"} +] lint = [ {name = "prek", specifier = "==0.3.13"}, {name = "pytest", specifier = ">=8.3"} @@ -611,8 +705,10 @@ tooling = [ [package.optional-dependencies] dev = [ + {name = "coverage"}, {name = "prek"}, - {name = "pytest"} + {name = "pytest"}, + {name = "pytest-cov"} ] [[package]] @@ -1847,6 +1943,20 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z"} ] +[[package]] +dependencies = [ + {name = "coverage"}, + {name = "pluggy"}, + {name = "pytest"} +] +name = "pytest-cov" +sdist = {url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z"} +source = {registry = "https://pypi.org/simple"} +version = "7.1.0" +wheels = [ + {url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z"} +] + [[package]] name = "python-crontab" sdist = {url = "https://files.pythonhosted.org/packages/99/7f/c54fb7e70b59844526aa4ae321e927a167678660ab51dda979955eafb89a/python_crontab-3.3.0.tar.gz", hash = "sha256:007c8aee68dddf3e04ec4dce0fac124b93bd68be7470fc95d2a9617a15de291b", size = 57626, upload-time = "2025-07-13T20:05:35.535Z"} From a90c76ec1bcf9cf7523b9af1c16c3b1671a9bdbe Mon Sep 17 00:00:00 2001 From: AuraMindNest Date: Tue, 19 May 2026 17:41:22 -0600 Subject: [PATCH 2/6] Update due to coderabbitai review --- .github/workflows/lint-and-format.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/lint-and-format.yml b/.github/workflows/lint-and-format.yml index 8025c0c..810ecff 100644 --- a/.github/workflows/lint-and-format.yml +++ b/.github/workflows/lint-and-format.yml @@ -10,7 +10,6 @@ on: permissions: contents: read - actions: write jobs: lint-and-format: From 5898f7b2c54a9a3879b560e9f37f77f7b4c44c4d Mon Sep 17 00:00:00 2001 From: AuraMindNest Date: Wed, 20 May 2026 09:37:01 -0600 Subject: [PATCH 3/6] Fix CI fail due to merge conflict resolve --- src/boost_weblate/settings_override.py | 7 ++++++- uv.lock | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/boost_weblate/settings_override.py b/src/boost_weblate/settings_override.py index 0a9cc6a..f8de81d 100644 --- a/src/boost_weblate/settings_override.py +++ b/src/boost_weblate/settings_override.py @@ -67,4 +67,9 @@ def weblate_formats_with_quickbook() -> tuple[str, ...]: _INSTALLED_APPS = globals().get("INSTALLED_APPS") if _INSTALLED_APPS is not None: - _INSTALLED_APPS += (_ENDPOINT_APP_CONFIG,) + # Tuple += creates a new object; assign back so exec namespace / settings see it. + # List += mutates in place, matching Weblate/Docker settings namespaces. + if isinstance(_INSTALLED_APPS, tuple): + globals()["INSTALLED_APPS"] = _INSTALLED_APPS + (_ENDPOINT_APP_CONFIG,) + else: + _INSTALLED_APPS += (_ENDPOINT_APP_CONFIG,) diff --git a/uv.lock b/uv.lock index a8da875..c98bddd 100644 --- a/uv.lock +++ b/uv.lock @@ -772,7 +772,7 @@ requires-dist = [ {name = "prek", marker = "extra == 'dev'", specifier = "==0.3.13"}, {name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3"}, {name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.1.0"}, - {name = "weblate", specifier = ">=5.16.0"} + {name = "weblate", extras = ["all"], specifier = "==5.17.1"} ] [package.metadata.requires-dev] From 23f63d877c6ab3f8270b34aac4241f8cc45a29e1 Mon Sep 17 00:00:00 2001 From: AuraMindNest Date: Wed, 20 May 2026 11:33:37 -0600 Subject: [PATCH 4/6] fix due to the first reviewer --- src/boost_weblate/settings_override.py | 16 ++++++++++------ tests/endpoint/test_endpoint.py | 23 +++++++++++++++++++---- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/boost_weblate/settings_override.py b/src/boost_weblate/settings_override.py index f8de81d..6922c77 100644 --- a/src/boost_weblate/settings_override.py +++ b/src/boost_weblate/settings_override.py @@ -13,13 +13,17 @@ regex-slicing ``FormatsConf.FORMATS``. That avoids ``import weblate.formats.models``, which pulls in Django ORM classes during settings import and raises ``AppRegistryNotReady``. The slice is **layout-sensitive**: it assumes ``FORMATS = (`` -inside ``FormatsConf`` is followed by ``class Meta:`` at the same indent; if upstream -reformats that class, update the pattern here. +inside ``FormatsConf`` (same file) is followed by ``class Meta:`` at the same indent; +if upstream reformats ``FormatsConf`` or moves ``FORMATS`` / ``Meta``, update +``_FORMATS_BLOCK`` below. -``INSTALLED_APPS`` is extended via ``globals().get("INSTALLED_APPS")`` when this file -is ``exec``'d (Docker): the list exists in the settings namespace. Importing this -module for tests still defines ``WEBLATE_FORMATS`` on the module without mutating -Django settings. +When this file is ``exec``'d into Weblate's settings namespace (Docker), +``INSTALLED_APPS`` is taken from ``globals()`` and extended. Upstream +``weblate.settings_docker`` uses a **list**; the override appends in place with +``+=``. Settings that use an **immutable tuple** instead get a new tuple assigned +back to ``globals()["INSTALLED_APPS"]``. Importing this module without +``INSTALLED_APPS`` in the namespace (typical unit tests) still defines +``WEBLATE_FORMATS`` and skips the apps mutation. """ from __future__ import annotations diff --git a/tests/endpoint/test_endpoint.py b/tests/endpoint/test_endpoint.py index bde35c6..d86b98c 100644 --- a/tests/endpoint/test_endpoint.py +++ b/tests/endpoint/test_endpoint.py @@ -98,9 +98,20 @@ def test_boost_endpoint_config_ready_registers_urls( assert len(fake.real_patterns) == 1 -def test_settings_override_exec_appends_format_and_endpoint_app() -> None: +@pytest.mark.parametrize( + "installed_apps", + [ + pytest.param((), id="tuple"), + pytest.param([], id="list"), + ], +) +def test_settings_override_exec_appends_format_and_endpoint_app( + installed_apps: tuple[str, ...] | list[str], +) -> None: """Load ``settings_override.py`` as ``boost_weblate.settings_override``. + Covers tuple (immutable reassignment) and list (Docker / in-place ``+=``). + Used so coverage attributes execution to the real file path. """ settings_path = _REPO_ROOT / "src" / "boost_weblate" / "settings_override.py" @@ -111,11 +122,11 @@ def test_settings_override_exec_appends_format_and_endpoint_app() -> None: assert spec is not None and spec.loader is not None module = importlib.util.module_from_spec(spec) module.__dict__["WEBLATE_FORMATS"] = () - module.__dict__["INSTALLED_APPS"] = () + module.__dict__["INSTALLED_APPS"] = installed_apps sys.modules[name] = module spec.loader.exec_module(module) formats = module.WEBLATE_FORMATS - apps_tuple = module.INSTALLED_APPS + apps_out = module.INSTALLED_APPS finally: if saved is not None: sys.modules[name] = saved @@ -124,4 +135,8 @@ def test_settings_override_exec_appends_format_and_endpoint_app() -> None: assert isinstance(formats, tuple) assert "boost_weblate.formats.quickbook.QuickBookFormat" in formats - assert "boost_weblate.endpoint.apps.BoostEndpointConfig" in apps_tuple + assert "boost_weblate.endpoint.apps.BoostEndpointConfig" in apps_out + if isinstance(installed_apps, list): + assert apps_out is installed_apps + else: + assert isinstance(apps_out, tuple) From 5d34689be46b1bedba36442f9e057f6876d1af2c Mon Sep 17 00:00:00 2001 From: AuraMindNest Date: Wed, 20 May 2026 14:59:06 -0600 Subject: [PATCH 5/6] Update Copyright --- .github/workflows/dep-audit.yml | 2 +- .github/workflows/lint-and-format.yml | 2 +- .pre-commit-config.yaml | 2 +- tests/endpoint/__init__.py | 2 +- tests/endpoint/test_endpoint.py | 2 +- tests/utils/__init__.py | 2 +- tests/utils/test_quickbook.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/dep-audit.yml b/.github/workflows/dep-audit.yml index 08aae3c..df02bef 100644 --- a/.github/workflows/dep-audit.yml +++ b/.github/workflows/dep-audit.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2026 Andrew Zhang +# SPDX-FileCopyrightText: 2026 William Jin # # SPDX-License-Identifier: BSL-1.0 diff --git a/.github/workflows/lint-and-format.yml b/.github/workflows/lint-and-format.yml index 810ecff..03405aa 100644 --- a/.github/workflows/lint-and-format.yml +++ b/.github/workflows/lint-and-format.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2026 Andrew Zhang +# SPDX-FileCopyrightText: 2026 William Jin # # SPDX-License-Identifier: BSL-1.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2697a6c..4027018 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2026 Andrew Zhang +# SPDX-FileCopyrightText: 2026 William Jin # # SPDX-License-Identifier: BSL-1.0 diff --git a/tests/endpoint/__init__.py b/tests/endpoint/__init__.py index 62d857b..b078a7d 100644 --- a/tests/endpoint/__init__.py +++ b/tests/endpoint/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2026 Andrew Zhang +# SPDX-FileCopyrightText: 2026 William Jin # # SPDX-License-Identifier: BSL-1.0 diff --git a/tests/endpoint/test_endpoint.py b/tests/endpoint/test_endpoint.py index d86b98c..01de036 100644 --- a/tests/endpoint/test_endpoint.py +++ b/tests/endpoint/test_endpoint.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2026 Andrew Zhang +# SPDX-FileCopyrightText: 2026 William Jin # # SPDX-License-Identifier: BSL-1.0 diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 62d857b..b078a7d 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2026 Andrew Zhang +# SPDX-FileCopyrightText: 2026 William Jin # # SPDX-License-Identifier: BSL-1.0 diff --git a/tests/utils/test_quickbook.py b/tests/utils/test_quickbook.py index bb60543..6e32255 100644 --- a/tests/utils/test_quickbook.py +++ b/tests/utils/test_quickbook.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2026 Andrew Zhang +# SPDX-FileCopyrightText: 2026 William Jin # # SPDX-License-Identifier: BSL-1.0 From c0fe41351d346ea25b2d4f0b807e6d312d032e3a Mon Sep 17 00:00:00 2001 From: AuraMindNest Date: Wed, 20 May 2026 15:01:51 -0600 Subject: [PATCH 6/6] Remove test_endpoint due to merge conflict fix --- tests/endpoint/test_endpoint.py | 142 -------------------------------- 1 file changed, 142 deletions(-) delete mode 100644 tests/endpoint/test_endpoint.py diff --git a/tests/endpoint/test_endpoint.py b/tests/endpoint/test_endpoint.py deleted file mode 100644 index 01de036..0000000 --- a/tests/endpoint/test_endpoint.py +++ /dev/null @@ -1,142 +0,0 @@ -# SPDX-FileCopyrightText: 2026 William Jin -# -# SPDX-License-Identifier: BSL-1.0 - -from __future__ import annotations - -import builtins -import importlib.util -import logging -import sys -import types -from pathlib import Path - -import pytest -from django.test import RequestFactory - -from boost_weblate.endpoint.views import plugin_ping - -_REPO_ROOT = Path(__file__).resolve().parents[2] - - -def test_register_plugin_urls_appends_once(monkeypatch: pytest.MonkeyPatch) -> None: - fake = types.ModuleType("weblate.urls") - fake.real_patterns = [] - fake._cppa_boost_weblate_urls_registered = False - monkeypatch.setitem(sys.modules, "weblate.urls", fake) - - from boost_weblate.endpoint.apps import register_plugin_urls - - register_plugin_urls() - register_plugin_urls() - - assert len(fake.real_patterns) == 1 - - -def test_plugin_ping_returns_200() -> None: - request = RequestFactory().get("/plugin-ping/") - response = plugin_ping(request) - assert response.status_code == 200 - assert response.content == b"ok" - - -def test_register_plugin_urls_skips_on_weblate_urls_import_error( - monkeypatch: pytest.MonkeyPatch, - caplog: pytest.LogCaptureFixture, -) -> None: - real_import = builtins.__import__ - - def fake_import( - name: str, - globals_arg: dict | None = None, - locals_arg: dict | None = None, - fromlist: tuple[str, ...] = (), - level: int = 0, - ): - if name == "weblate.urls": - msg = "No module named 'weblate.urls'" - raise ModuleNotFoundError(msg) - return real_import(name, globals_arg, locals_arg, fromlist, level) - - monkeypatch.setattr(builtins, "__import__", fake_import) - caplog.set_level(logging.DEBUG, logger="boost_weblate.endpoint.apps") - - from boost_weblate.endpoint.apps import register_plugin_urls - - register_plugin_urls() - assert "skipping URL registration" in caplog.text - - -def test_register_plugin_urls_warns_without_real_patterns( - monkeypatch: pytest.MonkeyPatch, - caplog: pytest.LogCaptureFixture, -) -> None: - fake = types.ModuleType("weblate.urls") - fake._cppa_boost_weblate_urls_registered = False - monkeypatch.setitem(sys.modules, "weblate.urls", fake) - - from boost_weblate.endpoint.apps import register_plugin_urls - - with caplog.at_level(logging.WARNING, logger="boost_weblate.endpoint.apps"): - register_plugin_urls() - assert "no real_patterns" in caplog.text - - -def test_boost_endpoint_config_ready_registers_urls( - monkeypatch: pytest.MonkeyPatch, -) -> None: - fake = types.ModuleType("weblate.urls") - fake.real_patterns = [] - fake._cppa_boost_weblate_urls_registered = False - monkeypatch.setitem(sys.modules, "weblate.urls", fake) - - import boost_weblate.endpoint.apps as apps_module - from boost_weblate.endpoint.apps import BoostEndpointConfig - - cfg = BoostEndpointConfig("boost_weblate.endpoint", apps_module) - cfg.ready() - assert len(fake.real_patterns) == 1 - - -@pytest.mark.parametrize( - "installed_apps", - [ - pytest.param((), id="tuple"), - pytest.param([], id="list"), - ], -) -def test_settings_override_exec_appends_format_and_endpoint_app( - installed_apps: tuple[str, ...] | list[str], -) -> None: - """Load ``settings_override.py`` as ``boost_weblate.settings_override``. - - Covers tuple (immutable reassignment) and list (Docker / in-place ``+=``). - - Used so coverage attributes execution to the real file path. - """ - settings_path = _REPO_ROOT / "src" / "boost_weblate" / "settings_override.py" - name = "boost_weblate.settings_override" - saved = sys.modules.pop(name, None) - try: - spec = importlib.util.spec_from_file_location(name, settings_path) - assert spec is not None and spec.loader is not None - module = importlib.util.module_from_spec(spec) - module.__dict__["WEBLATE_FORMATS"] = () - module.__dict__["INSTALLED_APPS"] = installed_apps - sys.modules[name] = module - spec.loader.exec_module(module) - formats = module.WEBLATE_FORMATS - apps_out = module.INSTALLED_APPS - finally: - if saved is not None: - sys.modules[name] = saved - else: - sys.modules.pop(name, None) - - assert isinstance(formats, tuple) - assert "boost_weblate.formats.quickbook.QuickBookFormat" in formats - assert "boost_weblate.endpoint.apps.BoostEndpointConfig" in apps_out - if isinstance(installed_apps, list): - assert apps_out is installed_apps - else: - assert isinstance(apps_out, tuple)