| 1 | """CONFIGURATION.md must not advertise X backends the engine does not have (#942). |
| 2 | |
| 3 | The X / Twitter row and the X env-var rows are the user-facing surface for the |
| 4 | X backend set, so they are held to ``env.X_BACKEND_KNOWN`` and to the official |
| 5 | X path copy (U8): the ``X_BEARER_TOKEN`` bearer path is described as about a |
| 6 | week, never as parity with the connector lane (R6). |
| 7 | """ |
| 8 | |
| 9 | import re |
| 10 | from pathlib import Path |
| 11 | |
| 12 | from lib import env |
| 13 | |
| 14 | REPO = Path(__file__).resolve().parents[1] |
| 15 | BEARER_COVERAGE = ( |
| 16 | "recent posts, about the last week, unless your X developer project has " |
| 17 | "full-archive access" |
| 18 | ) |
| 19 | |
| 20 | |
| 21 | def _configuration() -> str: |
| 22 | return (REPO / "CONFIGURATION.md").read_text(encoding="utf-8") |
| 23 | |
| 24 | |
| 25 | def _x_twitter_row() -> str: |
| 26 | for line in _configuration().splitlines(): |
| 27 | if line.startswith("| X / Twitter |"): |
| 28 | return line |
| 29 | raise AssertionError("CONFIGURATION.md is missing the X / Twitter table row") |
| 30 | |
| 31 | |
| 32 | def _env_row(var: str) -> str: |
| 33 | for line in _configuration().splitlines(): |
| 34 | if line.startswith(f"| `{var}` |"): |
| 35 | return line |
| 36 | raise AssertionError(f"CONFIGURATION.md is missing the env-var table row for {var}") |
| 37 | |
| 38 | |
| 39 | def test_configuration_x_row_omits_scrapecreators(): |
| 40 | row = _x_twitter_row() |
| 41 | assert "SCRAPECREATORS_API_KEY" not in row |
| 42 | assert "ScrapeCreators" not in row |
| 43 | |
| 44 | |
| 45 | def test_configuration_x_row_names_only_real_backends(): |
| 46 | """Every backend the row pins (LAST30DAYS_X_BACKEND=<name>) is a known backend.""" |
| 47 | row = _x_twitter_row() |
| 48 | pinned = re.findall(r"LAST30DAYS_X_BACKEND=([a-z]+)", row) |
| 49 | assert pinned, "the X row should show at least one opt-in pin" |
| 50 | assert set(pinned) <= set(env.X_BACKEND_KNOWN) |
| 51 | |
| 52 | |
| 53 | def test_configuration_x_row_lists_bearer_with_coverage_caveat(): |
| 54 | row = _x_twitter_row() |
| 55 | assert "X_BEARER_TOKEN" in row |
| 56 | assert "LAST30DAYS_X_BACKEND=xapi" in row, "xapi is opt-in outside Grok Bot" |
| 57 | assert BEARER_COVERAGE in row |
| 58 | # The row must not sell the bearer path as full-window parity. |
| 59 | assert "full 30-day" not in row.split("X_BEARER_TOKEN", 1)[1].split("`XAI_API_KEY`", 1)[0] |
| 60 | |
| 61 | |
| 62 | def test_configuration_env_rows_for_the_official_x_path(): |
| 63 | host = _env_row("LAST30DAYS_HOST") |
| 64 | assert "grok-bot" in host |
| 65 | bearer = _env_row("X_BEARER_TOKEN") |
| 66 | assert BEARER_COVERAGE in bearer |
| 67 | lane = _env_row("LAST30DAYS_X_HOST_LANE") |
| 68 | # KTD10: the lane signal is read from the process environment only. |
| 69 | assert "process environment" in lane |
| 70 | assert ".env" in lane and "ignored" in lane |
| 71 | |
| 72 | |
| 73 | def test_configuration_pin_row_names_xapi_and_the_grok_bot_rule(): |
| 74 | row = _env_row("LAST30DAYS_X_BACKEND") |
| 75 | assert "`xapi`" in row |
| 76 | # KTD7: on a Grok Bot host the pin is the only way outside the official chain. |
| 77 | assert "Grok Bot" in row |
| 78 | assert "only way" in row |
| 79 | assert "xapi → xai → xurl" in row |
| 80 | |
| 81 | |
| 82 | def test_engine_has_no_scrapecreators_x_backend(): |
| 83 | assert "scrapecreators" not in env.X_BACKEND_KNOWN |
| 84 | assert env._x_backend_available("scrapecreators", {"SCRAPECREATORS_API_KEY": "k"}, False) is False |
| 85 |