"""Unit tests: the shared error-string sanitizer (phase 84, task 01). ``app.core.errors.sanitize_error`` is the verbatim lift of the phase-32 sync masker — a NARROW ``user:pass@`` userinfo regex (git/HTTP convention) with the ``*****@`` replacement. These pins fix both sides of the contract: * masking — every pinned ``user:pass@`` occurrence is rewritten to ``*****@`` (a single userinfo, two in one string, and one inside a realistic git push-failure line — where ``fatal:`` / ``exit 128`` / the host survive); * byte-identity — credential-free text (a bare https URL without userinfo, prose with a colon + space, a plain email, the empty string) comes back unchanged, character for character; * idempotence — sanitizing an already-sanitized (masked) string is a no-op. Plus the refactor pin: ``app.api.sync._sanitize_error`` is the shared function under the private alias name (the phase-32 callers — ``app/api/git_sources.py``, ``app/api/docs.py`` — see the identical function through the import). """ from __future__ import annotations import app.api.sync as sync_api from app.core.errors import sanitize_error TWO_USERINFO = "https://alice:secret1@repo.example.com/a.git and https://bob:secret2@repo.example.com/b.git" GIT_PUSH_FAILURE = ( "git push failed (exit 128): fatal: Authentication failed for " "'https://bot:tok@github.com/o/r.git/'" ) # --- The masked forms (the regex must fire) -------------------------------- def test_masks_single_userinfo_in_url() -> None: """The canonical shape: ``user:pass@`` → ``*****@``, host intact.""" assert sanitize_error("https://user:pass@host/repo.git") == "https://*****@host/repo.git" def test_masks_every_userinfo_occurrence() -> None: """Two userinfo runs in one string → both masked, nothing else touched.""" masked = sanitize_error(TWO_USERINFO) assert masked == "https://*****@repo.example.com/a.git and https://*****@repo.example.com/b.git" assert "secret1" not in masked assert "secret2" not in masked def test_masks_token_in_git_push_failure_line() -> None: """The SEC-08 shape: git's auth-failure stderr with a token-bearing remote URL → the token is masked, the actionable context survives.""" masked = sanitize_error(GIT_PUSH_FAILURE) assert masked == ( "git push failed (exit 128): fatal: Authentication failed for " "'https://*****@github.com/o/r.git/'" ) assert "tok" not in masked assert "exit 128" in masked # the reason survives assert "fatal:" in masked assert "github.com/o/r.git" in masked # host + repo path survive # --- The byte-identity contract (credential-free text) --------------------- def test_plain_url_without_userinfo_is_unchanged() -> None: """A bare https URL (no userinfo) plus a realistic git failure line → byte-identical.""" message = ( "git push failed (exit 128): fatal: repository " "'https://github.com/owner/repo.git/' not found" ) assert sanitize_error(message) == message def test_prose_with_colon_and_space_is_unchanged() -> None: """Colons in prose are followed by spaces, not by userinfo runs — the narrow regex must not fire (the sync tests pin the same copy).""" model_message = ( "The embedding model ('embed') is not available — check the model endpoint and retry." ) assert sanitize_error(model_message) == model_message sentence = "note: the sync failed: see the log for details" assert sanitize_error(sentence) == sentence def test_email_in_prose_is_unchanged() -> None: """``owner@example.com`` carries no colon userinfo run before the ``@`` — the pattern does not match it, so it stays byte-identical (pinned for determinism: the masker only rewrites ``user:pass@``).""" message = "please contact owner@example.com about the failing push" assert sanitize_error(message) == message def test_empty_string_is_unchanged() -> None: assert sanitize_error("") == "" # --- Idempotence (masked forms) --------------------------------------------- def test_idempotent_on_masked_forms() -> None: """Sanitizing an already-sanitized string is a no-op — the ``*****@`` replacement never forms a new ``user:pass@`` run in the pinned cases.""" for message in ("https://user:pass@host/repo.git", TWO_USERINFO, GIT_PUSH_FAILURE): once = sanitize_error(message) assert sanitize_error(once) == once # --- The sync alias refactor (phase 84, task 01) ---------------------------- def test_sync_keeps_the_private_alias_of_the_shared_function() -> None: """``app.api.sync._sanitize_error`` IS the shared sanitizer under the private name — the phase-32 callers (``git_sources.py``, ``docs.py``) import it through ``app.api.sync`` and see the identical function.""" assert sync_api._sanitize_error is sanitize_error assert sync_api._sanitize_error(GIT_PUSH_FAILURE) == sanitize_error(GIT_PUSH_FAILURE)