fix(campaigns): let the connection test run on an unfunded account

Running the diagnostics against the real account returned exactly two
blockers — no payment method, and no operator token — and both of them
disabled the connection test, because the button was gated on `ready`.

That is backwards. The connection test creates PAUSED objects and deletes
them; Meta does not require a funding source for that, and an approval
identity has nothing to do with it. Gating on full readiness put the check
out of reach of the person who most needs it: someone with an unfunded
account trying to find out whether Meta accepts what MaskanX sends at all.
That question is the reason this feature exists — a live sync failed with
subcode 4834011 and nobody could tell why.

`can_smoke_test` reports the weaker condition, and `smoke_test_blocking`
names what would actually stop it, so the UI can say which check to fix
rather than "all of them".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
AFFAANh
2026-08-07 13:12:23 +05:30
co-authored by Claude Opus 5
parent 2479b94edf
commit e1bba0cac1
2 changed files with 77 additions and 0 deletions
+21
View File
@@ -238,11 +238,32 @@ async def run_diagnostics(meta_factory, ad_account_id: str | None) -> dict[str,
return _summarise(checks)
# The checks the smoke test actually depends on.
#
# It creates PAUSED objects and deletes them, so it needs a token, an ad
# account it can write to, and a Page to attach the creative to — but not a
# payment method, and not an operator identity. Gating it on full readiness
# would put it out of reach of the person who most needs it: someone whose
# account has no funding yet, trying to find out whether Meta accepts what
# MaskanX sends at all.
_SMOKE_TEST_CHECKS = frozenset(
{"Meta access token", "Token is valid", "Ad account", "Account status",
"Facebook Page"},
)
def _summarise(checks: list[Check]) -> dict[str, Any]:
failed = [c for c in checks if c.status == FAILED]
blocked_names = {c.name for c in failed}
return {
"ready": not failed,
"blocking": [c.name for c in failed],
# Separate from `ready`: a launch needs everything, a connection
# test needs only what it touches.
"can_smoke_test": not (blocked_names & _SMOKE_TEST_CHECKS),
"smoke_test_blocking": [
c.name for c in failed if c.name in _SMOKE_TEST_CHECKS
],
"checks": [asdict(c) for c in checks],
}
+56
View File
@@ -362,3 +362,59 @@ def test_every_env_var_a_remedy_names_is_offered_in_settings():
f"Environments, but they are not in _KEY_REGISTRY so no field is "
f"shown for them."
)
# --- what blocks a launch is not what blocks a connection test ---
async def test_a_missing_payment_method_does_not_block_the_connection_test(
configured,
):
"""The smoke test creates PAUSED objects. Meta does not need a funding
source for that, and someone with an unfunded account is exactly who
needs to find out whether the connection works at all."""
meta = FakeMeta(account={
"id": "act_1", "name": "Maskan-MetaAds", "currency": "INR",
"account_status": 1, "funding_source": None, "min_daily_budget": 9709,
})
report = await run_diagnostics(lambda: meta, None)
assert report["ready"] is False
assert "Payment method" in report["blocking"]
assert report["can_smoke_test"] is True
assert report["smoke_test_blocking"] == []
async def test_missing_operator_tokens_do_not_block_the_connection_test(
monkeypatch,
):
monkeypatch.setenv("META_ADS_ACCOUNT_ID", "act_1")
monkeypatch.setenv("META_PAGE_ID", "page_1")
monkeypatch.delenv("MASKANX_OPERATOR_TOKENS", raising=False)
report = await run_diagnostics(lambda: FakeMeta(), None)
assert "Operator identity" in report["blocking"]
assert report["can_smoke_test"] is True
async def test_an_unreachable_page_does_block_the_connection_test(configured):
"""Sync attaches the creative to a Page, so this one it genuinely needs."""
meta = FakeMeta()
meta.fail_page = True
report = await run_diagnostics(lambda: meta, None)
assert report["can_smoke_test"] is False
assert report["smoke_test_blocking"] == ["Facebook Page"]
async def test_no_token_blocks_the_connection_test(monkeypatch):
def no_token():
raise MetaNotConfiguredError("META_ADS_ACCESS_TOKEN is not set.")
report = await run_diagnostics(no_token, None)
assert report["can_smoke_test"] is False
assert report["smoke_test_blocking"] == ["Meta access token"]