"""End-to-end onboarding proof: a brand-new client, REAL extraction + REAL embeddings (no index mock), answering only from ITS OWN uploaded document, fully isolated from another client. This is the "can I onboard a real store and trust it" guarantee. Marked slow because it loads the embedding model. """ from __future__ import annotations import shutil from pathlib import Path import pytest from app.models import ChatSession from app.tenancy import get_tenant_by_slug from app.tools import knowledge_tool from app.tools.registry import ToolContext AUTH = {"Authorization": "Bearer test-admin-token"} # Distinctive facts with rare tokens so retrieval + isolation are unambiguous. CLIENT_A = { "slug": "mochilas-trekmax", "brand": "Mochilas TrekMax", "file": ("garantia.md", b"La garantia de la mochila TrekMax Summit es de siete anos. " b"El material es Cordura 1000D totalmente impermeable.", "text/markdown"), "ask": "cuanto dura la garantia de la mochila summit y de que material es", "must_have": "TrekMax", "must_not_have": "GotaFina", } CLIENT_B = { "slug": "riego-gotafina", "brand": "Riego GotaFina", "file": ("kit.md", b"El kit de riego GotaFina incluye 30 metros de manguera de " b"poliuretano y boquillas de laton macizo.", "text/markdown"), "ask": "que incluye el kit de riego y de que son las boquillas", "must_not_have": "TrekMax", "must_have": "GotaFina", } async def _onboard(client, db, c): # 1) Create the tenant with its brand (what the admin form does). created = await client.post( "/admin/tenants", headers=AUTH, json={"slug": c["slug"], "brand_name": c["brand"]}, ) assert created.status_code == 201, created.text assert created.json()["brand_name"] == c["brand"] assert created.json()["has_shopify_secret"] is False # 2) Set its Shopify credentials — secret is stored encrypted, never echoed. upd = await client.put( f"/admin/tenants/{c['slug']}", headers=AUTH, json={"shopify_shop": f"{c['slug']}.myshopify.com", "shopify_client_id": "id123", "shopify_client_secret": "shpss_super_secret"}, ) assert upd.status_code == 200 assert upd.json()["has_shopify_secret"] is True # 3) Upload its real document — REAL extract -> chunk -> embed -> store. up = await client.post( f"/admin/tenants/{c['slug']}/sources/file", headers=AUTH, files={"file": c["file"]}, ) assert up.status_code == 201, up.text assert up.json()["status"] == "indexed", up.json() assert up.json().get("error") in (None, "") tenant = await get_tenant_by_slug(db, c["slug"]) return tenant.id @pytest.mark.slow async def test_full_client_onboarding_and_isolation(app_client, db_session): _app, client = app_client try: a_id = await _onboard(client, db_session, CLIENT_A) b_id = await _onboard(client, db_session, CLIENT_B) async def ask(tenant_id, question): ctx = ToolContext( db=db_session, session=ChatSession(shop="x", tenant_id=tenant_id), tenant_id=tenant_id, ) return (await knowledge_tool.run({"query": question}, ctx))["context"] # Client A's bot answers from A's doc, and NEVER sees B's. a_ctx = await ask(a_id, CLIENT_A["ask"]) assert CLIENT_A["must_have"] in a_ctx, a_ctx assert CLIENT_A["must_not_have"] not in a_ctx, f"LEAK from other tenant: {a_ctx}" # Client B's bot answers from B's doc, and NEVER sees A's. b_ctx = await ask(b_id, CLIENT_B["ask"]) assert CLIENT_B["must_have"] in b_ctx, b_ctx assert CLIENT_B["must_not_have"] not in b_ctx, f"LEAK from other tenant: {b_ctx}" finally: shutil.rmtree(Path("uploads") / CLIENT_A["slug"], ignore_errors=True) shutil.rmtree(Path("uploads") / CLIENT_B["slug"], ignore_errors=True)