"""Phase 173 (task 04, C3): the one-time startup migration of a legacy credentials-bearing ``docs_repo``. Pure/unit level — no live DB: the ``app_settings`` read is a stubbed overlay engine (the ``test_settings_overlay`` fake-engine idiom), the write path is monkeypatched (``settings_migrate._write_rows`` — it captures the rows AND mirrors them into the stubbed read store, so ``resolve_settings()`` resolves the DB rows the migration wrote — the DB-shadowing pin), the env base comes from ``os.environ`` (``tests/ conftest.py`` pins ``BOR_DOCS_REPO=`` / ``BOR_SETTINGS_DB=0`` — the fixture flips the switch on per test), and the overlay state is reset by ``reset_state_for_tests()``. """ from __future__ import annotations import logging from collections.abc import Iterator import pytest from app.core import settings_migrate, settings_overlay from app.core.settings_overlay import resolve_settings #: The ONE INFO line the migration logs when it acts (AGENTS rule 10). _MIGRATED_LINE = ( "settings: migrated embedded docs-push credentials out of " "docs_repo into docs_user/docs_token" ) class _FakeStore: """A controllable ``app_settings`` store: the read (a fake overlay engine) and the write (the monkeypatched ``_write_rows``) share one dict — a migration write is visible to ``resolve_settings()`` on the next resolution (the DB-shadowing pin).""" def __init__(self, rows: dict[str, str] | None = None) -> None: self.rows = dict(rows or {}) self.writes: list[dict[str, str]] = [] def install(self, monkeypatch: pytest.MonkeyPatch) -> None: store = self class _Rows: def __iter__(self) -> object: return iter(sorted(store.rows.items())) class _FakeConn: def execute(self, _stmt: object) -> _Rows: return _Rows() def __enter__(self) -> _FakeConn: return self def __exit__(self, *exc: object) -> bool: return False class _FakeEngine: def connect(self) -> _FakeConn: # type: ignore[no-untyped-def] return _FakeConn() monkeypatch.setattr( settings_overlay, "_get_engine", lambda: _FakeEngine() ) def _fake_write(rows: dict[str, str]) -> None: store.writes.append(dict(rows)) store.rows.update(rows) monkeypatch.setattr(settings_migrate, "_write_rows", _fake_write) @pytest.fixture(autouse=True) def _clean_state(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: settings_overlay.reset_state_for_tests() monkeypatch.setenv("BOR_SETTINGS_DB", "1") monkeypatch.setenv("BOR_DOCS_REPO", "") monkeypatch.setenv("BOR_DOCS_USER", "") monkeypatch.setenv("BOR_DOCS_TOKEN", "") yield settings_overlay.reset_state_for_tests() def _migrated_lines(caplog: pytest.LogCaptureFixture) -> list[str]: """The ``app.settings`` records that carry the migration line.""" return [ record.getMessage() for record in caplog.records if _MIGRATED_LINE in record.getMessage() ] # --------------------------------------------------------------------------- # (a) the no-op shapes — clean value, malformed segment # --------------------------------------------------------------------------- def test_clean_env_url_is_a_noop(monkeypatch: pytest.MonkeyPatch) -> None: """(a) a clean env URL with no overrides: ``False``, no rows written (the pre-phase behavior stands untouched).""" store = _FakeStore() store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "https://host/r.git") assert settings_migrate.migrate_legacy_docs_creds() is False assert store.writes == [] assert store.rows == {} def test_clean_db_override_is_a_noop( monkeypatch: pytest.MonkeyPatch, ) -> None: """(a, the DB-source variant) a clean URL arriving as a DB override (the effective value, not the env): still a no-op.""" store = _FakeStore({"docs_repo": "https://host/r.git"}) store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "https://elsewhere/r.git") assert settings_migrate.migrate_legacy_docs_creds() is False assert store.writes == [] assert store.rows == {"docs_repo": "https://host/r.git"} def test_malformed_empty_user_is_a_noop( monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture ) -> None: """(d) a malformed ``://:@…`` (empty user) is NOT the legacy shape: ``False``, no rows, no log — the value stays as-is (nothing breaks, nothing half-migrates).""" store = _FakeStore() store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "https://:@host/r.git") with caplog.at_level(logging.INFO, logger="app.settings"): assert settings_migrate.migrate_legacy_docs_creds() is False assert store.writes == [] assert store.rows == {} assert _migrated_lines(caplog) == [] def test_malformed_empty_pass_is_a_noop( monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture ) -> None: """(d, the other malformation) an empty pass (``://u:@…``): the same no-op contract — no rows, no log.""" store = _FakeStore() store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "https://u:@host/r.git") with caplog.at_level(logging.INFO, logger="app.settings"): assert settings_migrate.migrate_legacy_docs_creds() is False assert store.writes == [] assert store.rows == {} assert _migrated_lines(caplog) == [] def test_subdomain_is_not_a_false_positive( monkeypatch: pytest.MonkeyPatch, ) -> None: """A ``://user:pass@``-shaped SUBDOMAIN (no trailing ``@`` in the URL) must not match — the character classes + the trailing ``@`` requirement do that job (the same contract the PUT guard pins).""" store = _FakeStore() store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "https://sub.domain.example.com/r.git") assert settings_migrate.migrate_legacy_docs_creds() is False assert store.writes == [] # --------------------------------------------------------------------------- # (b) the migration shape — split, DB-shadowing, the one INFO line # --------------------------------------------------------------------------- def test_legacy_env_url_is_split_into_three_rows( monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture ) -> None: """(b) a legacy env URL with no overrides: ``True``, exactly the three rows (clean repo / user / token), and ``resolve_settings()`` resolves the clean URL + the credentials FROM THE DB rows (they shadow the env — the env value still embeds the credentials, the effective value does not).""" store = _FakeStore() store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "https://u:pass@host/r.git") with caplog.at_level(logging.INFO, logger="app.settings"): assert settings_migrate.migrate_legacy_docs_creds() is True assert store.rows == { "docs_repo": "https://host/r.git", "docs_user": "u", "docs_token": "pass", } assert len(store.writes) == 1 # one write call, one row set # DB rows SHADOW the env: the effective value is the clean URL. effective = resolve_settings() assert effective.docs_repo == "https://host/r.git" assert effective.docs_user == "u" assert effective.docs_token == "pass" # (e) the INFO line is emitted exactly once — and it carries no # value (the log discipline, A4). assert _migrated_lines(caplog) == [_MIGRATED_LINE] for line in _migrated_lines(caplog): assert "pass" not in line def test_second_run_is_a_silent_noop( monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture ) -> None: """(b + idempotency) after a migration wrote the three rows, the second run finds the ``docs_token`` row → ``False``, no rows written, NO second INFO line (the one-line pin, e).""" store = _FakeStore() store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "https://u:pass@host/r.git") with caplog.at_level(logging.INFO, logger="app.settings"): assert settings_migrate.migrate_legacy_docs_creds() is True assert settings_migrate.migrate_legacy_docs_creds() is False assert len(store.writes) == 1 assert _migrated_lines(caplog) == [_MIGRATED_LINE] def test_legacy_value_from_a_db_override_is_split( monkeypatch: pytest.MonkeyPatch, ) -> None: """(b, the DB-source variant) the legacy value arriving as a DB ``docs_repo`` override (the effective value): the same split — the override row is rewritten clean, the credentials join the new rows.""" store = _FakeStore({"docs_repo": "https://u:pass@host/r.git"}) store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "") # env empty — DB is the source assert settings_migrate.migrate_legacy_docs_creds() is True assert store.rows == { "docs_repo": "https://host/r.git", "docs_user": "u", "docs_token": "pass", } effective = resolve_settings() assert effective.docs_repo == "https://host/r.git" # --------------------------------------------------------------------------- # (c) the clobber guard — an existing docs_token override stands # --------------------------------------------------------------------------- def test_existing_token_override_is_never_clobbered( monkeypatch: pytest.MonkeyPatch, ) -> None: """(c) a legacy env URL while a ``docs_token`` override already exists (owner-set, or a previous migration): ``False`` — the pre-existing rows are untouched (no clobber).""" store = _FakeStore({"docs_token": "owner-token"}) store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "https://u:pass@host/r.git") assert settings_migrate.migrate_legacy_docs_creds() is False assert store.writes == [] assert store.rows == {"docs_token": "owner-token"} # byte-identical # the effective value resolves the owner's token, not the URL's. assert resolve_settings().docs_token == "owner-token" def test_existing_token_override_with_clean_repo_is_a_noop( monkeypatch: pytest.MonkeyPatch, ) -> None: """(c, the clean variant) a token override with a clean repo (env or DB): ``False`` — nothing to do, nothing touched.""" store = _FakeStore({"docs_token": "owner-token"}) store.install(monkeypatch) monkeypatch.setenv("BOR_DOCS_REPO", "https://host/r.git") assert settings_migrate.migrate_legacy_docs_creds() is False assert store.writes == [] assert store.rows == {"docs_token": "owner-token"}