diff --git a/src/adclaw/app/routers/envs.py b/src/adclaw/app/routers/envs.py index 3453537..45378ec 100644 --- a/src/adclaw/app/routers/envs.py +++ b/src/adclaw/app/routers/envs.py @@ -226,6 +226,11 @@ _KEY_REGISTRY: List[Dict[str, str]] = [ # Advertising {"key": "GOOGLE_ADS_DEVELOPER_TOKEN", "plugin": "Google Ads", "description": "Google Ads API developer token"}, {"key": "META_ADS_ACCESS_TOKEN", "plugin": "Meta Ads", "description": "Facebook & Instagram ads access token"}, + # Both of these block a campaign launch, and the Campaigns setup panel + # tells the operator to set them here — so they have to be listed here, + # or that instruction points at a page where the field is not offered. + {"key": "META_ADS_ACCOUNT_ID", "plugin": "Meta Ads", "description": "Ad account campaigns are created in, like act_1234567890"}, + {"key": "META_PAGE_ID", "plugin": "Meta Ads", "description": "Facebook Page ads are published from; sync refuses without it"}, {"key": "META_APP_ID", "plugin": "Meta Ads", "description": "Meta app ID, enables automatic token refresh"}, {"key": "META_APP_SECRET", "plugin": "Meta Ads", "description": "Meta app secret, enables automatic token refresh"}, {"key": "MASKANX_OPERATOR_TOKENS", "plugin": "Campaigns", "description": "Comma-separated name:token pairs authorised to approve and launch campaigns"}, diff --git a/tests/test_campaign_diagnostics.py b/tests/test_campaign_diagnostics.py index 47302b7..ba91493 100644 --- a/tests/test_campaign_diagnostics.py +++ b/tests/test_campaign_diagnostics.py @@ -330,3 +330,35 @@ def test_smoke_test_refuses_when_no_ad_account_is_configured( assert response.status_code == 422 assert "META_ADS_ACCOUNT_ID" in response.json()["detail"] + + +# --- the remedies have to point somewhere real --- + + +def test_every_env_var_a_remedy_names_is_offered_in_settings(): + """A remedy saying "set X in Settings > Environments" is a lie if X is + not in the key registry the Settings page renders. + + Arbitrary keys can be stored, but only registry entries are shown, so + an unlisted key leaves the operator with an instruction and no field. + """ + import re + + from adclaw.app.routers.envs import _KEY_REGISTRY + from adclaw.campaigns import diagnostics + + listed = {entry["key"] for entry in _KEY_REGISTRY} + source = (diagnostics.__file__ or "") + with open(source, encoding="utf-8") as handle: + text = handle.read() + + # Env var names as they appear in the remedy strings. + named = set(re.findall(r"\b(META_[A-Z_]+|MASKANX_[A-Z_]+)\b", text)) + named.discard("META_ADS_ACCOUNT_ID_ENV") + + missing = sorted(named - listed) + assert not missing, ( + f"Diagnostics tells the operator to set {missing} in Settings > " + f"Environments, but they are not in _KEY_REGISTRY so no field is " + f"shown for them." + )