| 1 | import os |
| 2 | import sys |
| 3 | from pathlib import Path |
| 4 | from unittest import mock |
| 5 | |
| 6 | import pytest |
| 7 | |
| 8 | sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "skills" / "last30days" / "scripts")) |
| 9 | |
| 10 | |
| 11 | @pytest.fixture(autouse=True) |
| 12 | def _no_arctic_network(): |
| 13 | """Default the arctic-shift score lookup to a no-op so the suite never makes |
| 14 | a real network call. test_reddit_arctic overrides this fixture (same name) to |
| 15 | exercise the real lookup with http.get mocked.""" |
| 16 | with mock.patch("lib.reddit_arctic.fetch_scores", return_value={}): |
| 17 | yield |
| 18 | |
| 19 | |
| 20 | @pytest.fixture(autouse=True) |
| 21 | def _no_ambient_grok_cli(): |
| 22 | """Default the grok CLI to absent so no test resolves it from the developer's |
| 23 | own machine. grok is a new X-chain backend whose availability is a plain |
| 24 | filesystem check, so a machine with it installed and signed in would |
| 25 | otherwise silently change chain resolution in every existing X test. |
| 26 | Tests that exercise grok stub these themselves (test_grok_x, |
| 27 | test_backend_descriptors._x_env) and override this by patching inside the |
| 28 | test body.""" |
| 29 | # Stub the input (PATH resolution), not the logic: has_stored_auth and |
| 30 | # _is_available_uncached then both resolve absent on their own, leaving the |
| 31 | # module's real control flow intact for tests that exercise it. |
| 32 | with mock.patch("lib.grok_x.binary_path", return_value=None): |
| 33 | yield |
| 34 | |
| 35 | |
| 36 | @pytest.fixture(autouse=True) |
| 37 | def _reset_probe_caches(): |
| 38 | """The doctor stack memoizes probe results in module-level dicts (safe for |
| 39 | the one-shot CLI process, wrong across tests). Clear them around every test |
| 40 | so a probe cached by one test can never leak into another.""" |
| 41 | from lib import grok_x, health, xurl_x |
| 42 | |
| 43 | health.clear_dependency_probe_cache() |
| 44 | xurl_x.clear_availability_cache() |
| 45 | grok_x.clear_availability_cache() |
| 46 | yield |
| 47 | health.clear_dependency_probe_cache() |
| 48 | xurl_x.clear_availability_cache() |
| 49 | grok_x.clear_availability_cache() |
| 50 | |
| 51 | @pytest.fixture(autouse=True) |
| 52 | def _reset_reddit_keyless_memo(): |
| 53 | """The keyless Reddit memo lives for one command; tests are their own commands.""" |
| 54 | from lib import http as _http |
| 55 | |
| 56 | _http.reset_reddit_keyless_memo() |
| 57 | yield |
| 58 | _http.reset_reddit_keyless_memo() |
| 59 | |
| 60 | |
| 61 | @pytest.fixture(autouse=True) |
| 62 | def _no_ambient_credentials(monkeypatch): |
| 63 | """Strip credential-shaped variables from the process environment. |
| 64 | |
| 65 | ``env.get_config()`` reads API keys and cookies straight from os.environ, so |
| 66 | a developer machine with real credentials exported resolves config the CI |
| 67 | box never would (upstream CI is a clean Linux runner). Tests that need a |
| 68 | credential set it themselves; nothing should depend on the ambient one. |
| 69 | """ |
| 70 | suffixes = ("_API_KEY", "_TOKEN", "_KEY", "_SECRET", "_PASSWORD", "_COOKIE") |
| 71 | exact = {"AUTH_TOKEN", "CT0", "SESSION_ID"} |
| 72 | for name in list(os.environ): |
| 73 | if name in exact or name.endswith(suffixes): |
| 74 | monkeypatch.delenv(name, raising=False) |
| 75 | yield |
| 76 |