diff options
| author | Yuriy Andamasov <yuriy@vyos.io> | 2026-05-06 23:22:11 +0300 |
|---|---|---|
| committer | Yuriy Andamasov <yuriy@vyos.io> | 2026-05-06 23:22:11 +0300 |
| commit | d1e5b5126af907215708fde817dcd309e579b725 (patch) | |
| tree | e742f915863d78461ca7cc97ee9af355fa745f7e /tests | |
| parent | 4cd5e2cc3bb38b75d4e47aacd6768218daf8762a (diff) | |
| parent | 7cf51e1c2901f6d1b01e9bff194f7188bc29e417 (diff) | |
| download | vyos-documentation-d1e5b5126af907215708fde817dcd309e579b725.tar.gz vyos-documentation-d1e5b5126af907215708fde817dcd309e579b725.zip | |
Merge remote-tracking branch 'origin/current' into fix/codecopier-snippet-leak-and-clipboard-error
# Conflicts:
# docs/_static/js/codecopier.js
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_swap_sources.py | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/tests/test_swap_sources.py b/tests/test_swap_sources.py new file mode 100644 index 00000000..7e9f68be --- /dev/null +++ b/tests/test_swap_sources.py @@ -0,0 +1,245 @@ +"""Tests for swap_sources.py — run with: + cd tests && PYTHONPATH=../scripts python -m pytest test_swap_sources.py -v + +In the inverted model MD is the canonical source for migrated pages and RST +under the ``rst-<stem>.rst`` prefix is a fallback used only for stems listed +in ``docs/_rst_overrides.txt``. ``do_swap`` activates the override (renames +``rst-<stem>.rst`` → ``<stem>.rst`` and excludes ``<stem>.md``); ``do_restore`` +undoes that. +""" +import json +import os +from pathlib import Path + +import pytest + + +# --------------------------------------------------------------------------- +# Helper +# --------------------------------------------------------------------------- + +def _setup_docs(tmp_path: Path, stems: list[str]) -> Path: + """Create a minimal docs/ tree with MD primary + rst-prefixed RST for each stem.""" + docs = tmp_path / "docs" + for stem in stems: + p = Path(stem) + name = p.name + parent = p.parent # may be "." for flat stems + target_dir = docs / parent if str(parent) != "." else docs + target_dir.mkdir(parents=True, exist_ok=True) + # MD file is the canonical source after the flip. + (target_dir / f"{name}.md").write_text(f"# {name}\nDummy MyST for {stem}\n") + # rst-prefixed RST file waiting to be activated as an override. + (target_dir / f"rst-{name}.rst").write_text(f".. _{name}:\n\nDummy RST for {stem}\n") + return docs + + +# --------------------------------------------------------------------------- +# parse_override_list +# --------------------------------------------------------------------------- + +def test_parse_override_list_basic(tmp_path): + from swap_sources import parse_override_list + + overrides_txt = tmp_path / "_rst_overrides.txt" + overrides_txt.write_text( + "# comment\n" + "\n" + "configuration/firewall/zone\n" + " quick-start \n" # leading/trailing whitespace + "# another comment\n" + "installation/virtual/vmware\n" + ) + stems = parse_override_list(overrides_txt) + assert stems == [ + "configuration/firewall/zone", + "quick-start", + "installation/virtual/vmware", + ] + + +def test_parse_override_list_empty(tmp_path): + from swap_sources import parse_override_list + + missing = tmp_path / "_rst_overrides.txt" + assert parse_override_list(missing) == [] + + +# --------------------------------------------------------------------------- +# do_swap (apply RST overrides) +# --------------------------------------------------------------------------- + +def test_swap_renames_files(tmp_path): + from swap_sources import do_swap + + stems = ["quick-start"] + docs = _setup_docs(tmp_path, stems) + (docs / "_rst_overrides.txt").write_text("quick-start\n") + + do_swap(docs) + + assert (docs / "quick-start.rst").exists(), "rst- file should be renamed to plain .rst" + assert not (docs / "rst-quick-start.rst").exists(), "rst- prefix file should be gone" + assert (docs / "quick-start.md").exists(), "MD file should still exist (just excluded)" + + +def test_swap_creates_state_file(tmp_path): + from swap_sources import do_swap + + stems = ["quick-start"] + docs = _setup_docs(tmp_path, stems) + (docs / "_rst_overrides.txt").write_text("quick-start\n") + + do_swap(docs) + + state_file = docs / "_build" / "_rst_override_state.json" + assert state_file.exists(), "state file should be created in _build/" + + state = json.loads(state_file.read_text()) + assert state["version"] == 2 + assert len(state["overrides"]) == 1 + + entry = state["overrides"][0] + assert entry["stem"] == "quick-start" + assert "rst_from" in entry + assert "rst_to" in entry + assert "md_excluded" in entry + + +def test_swap_creates_exclude_file(tmp_path): + from swap_sources import do_swap + + stems = ["quick-start"] + docs = _setup_docs(tmp_path, stems) + (docs / "_rst_overrides.txt").write_text("quick-start\n") + + do_swap(docs) + + exclude_file = docs / "_build" / "_md_exclude.txt" + assert exclude_file.exists(), "exclude file should be created in _build/" + + lines = [l.strip() for l in exclude_file.read_text().splitlines() if l.strip()] + assert "quick-start.md" in lines + + +# --------------------------------------------------------------------------- +# do_restore +# --------------------------------------------------------------------------- + +def test_restore_undoes_renames(tmp_path): + from swap_sources import do_restore, do_swap + + stems = ["quick-start"] + docs = _setup_docs(tmp_path, stems) + (docs / "_rst_overrides.txt").write_text("quick-start\n") + + do_swap(docs) + assert (docs / "quick-start.rst").exists() + + do_restore(docs) + + assert (docs / "rst-quick-start.rst").exists(), "rst- prefix file should be restored" + assert not (docs / "quick-start.rst").exists(), "plain .rst should be gone after restore" + assert (docs / "quick-start.md").exists(), "MD file should remain untouched throughout" + + state_file = docs / "_build" / "_rst_override_state.json" + exclude_file = docs / "_build" / "_md_exclude.txt" + assert not state_file.exists(), "state file should be deleted after restore" + assert not exclude_file.exists(), "exclude file should be deleted after restore" + + +def test_restore_noop_without_state(tmp_path): + from swap_sources import do_restore + + docs = tmp_path / "docs" + docs.mkdir() + # Should not raise even with no state file + do_restore(docs) # no error = pass + + +# --------------------------------------------------------------------------- +# Collision / validation +# --------------------------------------------------------------------------- + +def test_swap_fails_on_collision(tmp_path): + from swap_sources import do_swap + + stems = ["quick-start", "cli"] + docs = _setup_docs(tmp_path, stems) + (docs / "_rst_overrides.txt").write_text("quick-start\ncli\n") + + # Simulate a collision: unprefixed .rst already exists for "cli" + (docs / "cli.rst").write_text(".. _cli:\nCollision RST\n") + + with pytest.raises(RuntimeError, match="collision"): + do_swap(docs) + + # Rollback: first stem's rename should be undone + assert (docs / "rst-quick-start.rst").exists(), "rollback should undo completed renames" + assert not (docs / "quick-start.rst").exists(), "renamed file should not exist after rollback" + + # State file should NOT be written on failure + state_file = docs / "_build" / "_rst_override_state.json" + assert not state_file.exists(), "no state file should be written on failure" + + +# --------------------------------------------------------------------------- +# Rollback on rename failure (monkeypatched os.rename) +# --------------------------------------------------------------------------- + +def test_swap_rollback_on_rename_failure(tmp_path, monkeypatch): + """If os.rename raises on the second call, the first rename must be undone.""" + from swap_sources import do_swap + + stems = ["quick-start", "cli"] + docs = _setup_docs(tmp_path, stems) + (docs / "_rst_overrides.txt").write_text("quick-start\ncli\n") + + call_count = {"n": 0} + real_rename = os.rename + + def failing_rename(src, dst): + call_count["n"] += 1 + if call_count["n"] == 2: + raise OSError("simulated rename failure") + real_rename(src, dst) + + monkeypatch.setattr(os, "rename", failing_rename) + + with pytest.raises(RuntimeError): + do_swap(docs) + + # First rename (quick-start) should have been rolled back + assert (docs / "rst-quick-start.rst").exists(), "rollback should undo the first rename" + assert not (docs / "quick-start.rst").exists(), "renamed file should not exist after rollback" + + # No state file should exist + assert not (docs / "_build" / "_rst_override_state.json").exists() + + +# --------------------------------------------------------------------------- +# Nested paths +# --------------------------------------------------------------------------- + +def test_swap_nested_paths(tmp_path): + from swap_sources import do_swap, do_restore + + stems = ["configuration/firewall/zone"] + docs = _setup_docs(tmp_path, stems) + (docs / "_rst_overrides.txt").write_text("configuration/firewall/zone\n") + + do_swap(docs) + + activated = docs / "configuration" / "firewall" / "zone.rst" + rst_source = docs / "configuration" / "firewall" / "rst-zone.rst" + assert activated.exists(), "nested rst file should be renamed" + assert not rst_source.exists() + + exclude_file = docs / "_build" / "_md_exclude.txt" + lines = [l.strip() for l in exclude_file.read_text().splitlines() if l.strip()] + assert "configuration/firewall/zone.md" in lines + + do_restore(docs) + + assert rst_source.exists(), "nested rst- file should be restored" + assert not activated.exists() |
