feat(audit): free public store-support audit tool + programmatic-SEO pages (/audit)
0937536 verified | """Trial sweep: pause expired self-serve trials that never subscribed, email the | |
| payment link, and never touch paid or still-running trials.""" | |
| from __future__ import annotations | |
| from datetime import UTC, datetime, timedelta | |
| from sqlalchemy import select | |
| from app.models import Tenant | |
| from app.scheduler import pause_expired_trials | |
| async def test_pause_only_expired_unsubscribed_trials(db_session, monkeypatch): | |
| from app import mailer | |
| sent = [] | |
| async def fake(settings, to, subject, body, **k): | |
| sent.append(to) | |
| return True | |
| monkeypatch.setattr(mailer, "send_email", fake) | |
| past = datetime.now(UTC) - timedelta(hours=1) | |
| future = datetime.now(UTC) + timedelta(days=5) | |
| db_session.add_all([ | |
| Tenant(slug="t-expired", trial_ends_at=past, chatbot_enabled=True, | |
| support_email="e@x.com"), | |
| Tenant(slug="t-paid", trial_ends_at=past, chatbot_enabled=True, | |
| stripe_subscription_id="sub_123", support_email="p@x.com"), | |
| Tenant(slug="t-running", trial_ends_at=future, chatbot_enabled=True, | |
| support_email="r@x.com"), | |
| ]) | |
| await db_session.commit() | |
| n = await pause_expired_trials(db_session) | |
| assert n == 1 # only the expired, unsubscribed one | |
| db_session.expire_all() | |
| async def _get(slug): | |
| return (await db_session.execute(select(Tenant).where(Tenant.slug == slug))).scalar_one() | |
| expired = await _get("t-expired") | |
| assert expired.chatbot_enabled is False | |
| assert expired.disabled_reason == "trial" # kill-switch reason, cuts service everywhere | |
| assert (await _get("t-paid")).chatbot_enabled is True # subscribed -> kept | |
| assert (await _get("t-running")).chatbot_enabled is True # not expired yet | |
| assert "e@x.com" in sent # expired trial got the pay link | |
| assert "p@x.com" not in sent # paid one never emailed | |
| assert "r@x.com" not in sent | |
| async def test_pause_is_idempotent(db_session, monkeypatch): | |
| from app import mailer | |
| monkeypatch.setattr(mailer, "send_email", lambda *a, **k: _noop()) | |
| db_session.add(Tenant(slug="t-x", trial_ends_at=datetime.now(UTC) - timedelta(hours=1), | |
| chatbot_enabled=True, support_email="x@x.com")) | |
| await db_session.commit() | |
| assert await pause_expired_trials(db_session) == 1 | |
| # second sweep: already paused (chatbot_enabled=False) -> not reselected | |
| assert await pause_expired_trials(db_session) == 0 | |
| async def test_pause_excludes_shopify_installs(db_session, monkeypatch): | |
| """Shopify App Store installs are billed via the Shopify Billing API, never | |
| Stripe — the trial sweep must NEVER pause them or email the Stripe pay link | |
| (App Store policy 1.2.1). Direct/Stripe trials keep being swept as before.""" | |
| from app import mailer | |
| sent = [] | |
| async def fake(settings, to, subject, body, **k): | |
| sent.append(to) | |
| return True | |
| monkeypatch.setattr(mailer, "send_email", fake) | |
| past = datetime.now(UTC) - timedelta(hours=1) | |
| db_session.add_all([ | |
| Tenant(slug="t-sh-shop", trial_ends_at=past, chatbot_enabled=True, | |
| support_email="s1@x.com", shopify_shop="s1.myshopify.com", | |
| shopify_install_source="oauth"), | |
| Tenant(slug="t-sh-src", trial_ends_at=past, chatbot_enabled=True, | |
| support_email="s2@x.com", shopify_install_source="oauth"), | |
| Tenant(slug="t-direct", trial_ends_at=past, chatbot_enabled=True, | |
| support_email="d@x.com"), # direct/Stripe -> still swept | |
| ]) | |
| await db_session.commit() | |
| assert await pause_expired_trials(db_session) == 1 # only the direct one | |
| db_session.expire_all() | |
| async def _get(slug): | |
| return (await db_session.execute(select(Tenant).where(Tenant.slug == slug))).scalar_one() | |
| assert (await _get("t-sh-shop")).chatbot_enabled is True # Shopify -> untouched | |
| assert (await _get("t-sh-src")).chatbot_enabled is True | |
| assert (await _get("t-direct")).chatbot_enabled is False # direct -> paused | |
| assert "s1@x.com" not in sent and "s2@x.com" not in sent # no Stripe email to Shopify | |
| assert "d@x.com" in sent # direct still emailed | |
| async def _noop(): | |
| return True | |