flexigo-support-bot / tests /test_demo_purge.py
victor34593993's picture
instant demo feature
cd1d8fb verified
Raw
History Blame Contribute Delete
6.09 kB
"""Ephemeral demo tenants self-destruct: the daily scheduler sweep purges
expired demos WITH all their child rows, never touches live demos or real
clients, and the weekly reindex spends zero crawl resources on demos.
"""
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from sqlalchemy import func, select
from app.models import ChatMessage, ChatSession, KnowledgeSource, Tenant
from app.scheduler import purge_expired_demos, reindex_all_urls
def _expired() -> datetime:
return datetime.now(UTC) - timedelta(hours=1)
def _alive() -> datetime:
return datetime.now(UTC) + timedelta(hours=47)
async def _demo_tenant(db, slug: str, expires_at: datetime) -> Tenant:
t = Tenant(slug=slug, name=slug, is_demo=True, demo_expires_at=expires_at)
db.add(t)
await db.commit()
return t
async def _count(db, model, *where) -> int:
return (
await db.execute(select(func.count()).select_from(model).where(*where))
).scalar_one()
async def test_expired_demo_purged_with_all_children(db_session):
"""An expired demo tenant disappears completely: tenant row, knowledge
source, chat session AND chat messages — verified present before, gone
after."""
t = await _demo_tenant(db_session, "demo-caducada", _expired())
session = ChatSession(tenant_id=t.id)
db_session.add_all([
KnowledgeSource(
tenant_id=t.id, kind="url", name="web", location="https://demo.example"
),
session,
])
await db_session.flush()
db_session.add(ChatMessage(session_id=session.id, role="user", content="hola"))
await db_session.commit()
# The rows really exist BEFORE the purge — the test cannot pass vacuously.
assert await _count(db_session, Tenant, Tenant.slug == "demo-caducada") == 1
assert await _count(db_session, KnowledgeSource, KnowledgeSource.tenant_id == t.id) == 1
assert await _count(db_session, ChatSession, ChatSession.tenant_id == t.id) == 1
assert await _count(db_session, ChatMessage, ChatMessage.session_id == session.id) == 1
tenant_id = t.id
purged = await purge_expired_demos(db_session)
assert purged == 1 # exactly the expired demo was acted on
db_session.expire_all()
assert await _count(db_session, Tenant, Tenant.slug == "demo-caducada") == 0
assert await _count(db_session, KnowledgeSource, KnowledgeSource.tenant_id == tenant_id) == 0
assert await _count(db_session, ChatSession, ChatSession.tenant_id == tenant_id) == 0
assert await _count(db_session, ChatMessage, ChatMessage.session_id == session.id) == 0
async def test_unexpired_demo_and_real_tenant_survive_purge_run(db_session):
"""One purge run with all three kinds of tenant: the expired demo is
removed while the still-running demo and the real client keep their rows
AND their children intact."""
await _demo_tenant(db_session, "demo-vieja", _expired())
fresh = await _demo_tenant(db_session, "demo-fresca", _alive())
real = Tenant(slug="cliente-real", name="Cliente Real") # is_demo defaults False
db_session.add(real)
await db_session.commit()
db_session.add_all([
KnowledgeSource(
tenant_id=fresh.id, kind="url", name="f", location="https://fresca.example"
),
KnowledgeSource(
tenant_id=real.id, kind="url", name="r", location="https://real.example"
),
ChatSession(tenant_id=fresh.id),
ChatSession(tenant_id=real.id),
])
await db_session.commit()
purged = await purge_expired_demos(db_session)
assert purged == 1 # the run DID purge — surviving is a decision, not a no-op
db_session.expire_all()
assert await _count(db_session, Tenant, Tenant.slug == "demo-vieja") == 0
# The live demo survives, still flagged and still scheduled to expire.
survivor_demo = (
await db_session.execute(select(Tenant).where(Tenant.slug == "demo-fresca"))
).scalar_one()
assert survivor_demo.is_demo is True
assert survivor_demo.demo_expires_at is not None
assert await _count(
db_session, KnowledgeSource, KnowledgeSource.tenant_id == survivor_demo.id
) == 1
assert await _count(
db_session, ChatSession, ChatSession.tenant_id == survivor_demo.id
) == 1
# The real client is untouched, children included.
survivor_real = (
await db_session.execute(select(Tenant).where(Tenant.slug == "cliente-real"))
).scalar_one()
assert survivor_real.is_demo is False
assert await _count(
db_session, KnowledgeSource, KnowledgeSource.tenant_id == survivor_real.id
) == 1
assert await _count(
db_session, ChatSession, ChatSession.tenant_id == survivor_real.id
) == 1
async def test_reindex_skips_demo_tenant_sources(db_session, monkeypatch):
"""The periodic recrawl refreshes the real tenant's URL source and never
touches the 48h demo's one — demos get indexed once at creation, full
stop."""
from app.rag import index
real = Tenant(slug="tienda-real", name="Tienda Real")
demo = Tenant(
slug="demo-viva", name="Demo Viva", is_demo=True, demo_expires_at=_alive()
)
db_session.add_all([real, demo])
await db_session.commit()
db_session.add_all([
KnowledgeSource(
tenant_id=real.id, kind="url", name="r", location="https://tienda.example"
),
KnowledgeSource(
tenant_id=demo.id, kind="url", name="d", location="https://demo-viva.example"
),
])
await db_session.commit()
indexed: list[str] = []
async def fake_index_source(db, src):
indexed.append(src.location)
src.status = "indexed"
return src
monkeypatch.setattr(index, "index_source", fake_index_source)
done = await reindex_all_urls(db_session)
# Positive: the real tenant's URL really was refreshed...
assert done == 1
assert indexed == ["https://tienda.example"]
# ...and the demo tenant's URL was not crawled at all.
assert "https://demo-viva.example" not in indexed