"""Every per-tenant personalization must be UNIQUE to its account and never bleed into another tenant's widget/portal. Two fully-distinct stores are set up and we assert each surface returns ONLY its own values.""" from __future__ import annotations AUTH = {"Authorization": "Bearer test-admin-token"} ALPHA = { "brand_name": "Alpha Store", "assistant_name": "Ana", "brand_color": "#112233", "welcome": "Bienvenido a Alpha", "avatar_url": "https://img/alpha.png", "widget_mode": "both", "whatsapp_number": "+34111111111", "support_email": "owner@alpha.com", "starters": ["Comprar Alpha", "Pedido Alpha"], "shopify_shop": "alpha.myshopify.com", "shopify_client_id": "alpha-id", "shopify_client_secret": "alpha-secret", } BETA = { "brand_name": "Beta Servicios", "assistant_name": "Bruno", "brand_color": "#998877", "welcome": "Hola, soy Beta", "avatar_url": "https://img/beta.png", "widget_mode": "chat", "whatsapp_number": "+34222222222", "support_email": "owner@beta.com", "starters": ["Pedir presupuesto Beta"], } async def _make(client, slug, cfg): await client.post("/admin/tenants", headers=AUTH, json={"slug": slug}) tok = (await client.post(f"/admin/tenants/{slug}/dashboard-token", headers=AUTH)).json()[ "dashboard_token" ] r = await client.put("/portal/api/config", headers={"Authorization": "Bearer " + tok}, json=cfg) assert r.status_code == 200, r.text return tok async def test_every_personalization_is_unique_per_account(app_client): _app, client = app_client a_tok = await _make(client, "alpha", ALPHA) b_tok = await _make(client, "beta", BETA) # ── widget-config (what the public widget reads) returns ONLY own values ── wa = (await client.get("/widget-config?t=alpha")).json() wb = (await client.get("/widget-config?t=beta")).json() for field in ("brand_name", "assistant_name", "brand_color", "welcome", "avatar_url", "widget_mode", "whatsapp_number"): assert wa[field] == ALPHA[field], field assert wb[field] == BETA[field], field assert wa[field] != wb[field], f"{field} bled between tenants" assert wa["starters"] == ALPHA["starters"] assert wb["starters"] == BETA["starters"] # Alpha's values must NOT appear in Beta's config and vice-versa assert "Alpha" not in str(wb) and "Ana" not in str(wb) assert "Beta" not in str(wa) and "Bruno" not in str(wa) # ── portal config (each owner with their own token) returns ONLY own ── ca = (await client.get("/portal/api/config", headers={"Authorization": "Bearer " + a_tok})).json() cb = (await client.get("/portal/api/config", headers={"Authorization": "Bearer " + b_tok})).json() assert ca["slug"] == "alpha" and cb["slug"] == "beta" assert ca["support_email"] == "owner@alpha.com" and cb["support_email"] == "owner@beta.com" assert ca["shopify_connected"] is True # alpha set Shopify creds assert cb["shopify_connected"] is False # beta did not assert ca["shopify_shop"] == "alpha.myshopify.com" assert cb["shopify_shop"] in ("", None) # secrets never leak in either direction assert "alpha-secret" not in str(ca) and "alpha-secret" not in str(cb) # ── a token only ever reads ITS OWN config (cross-token gives the other's own) ── cross = (await client.get("/portal/api/config", headers={"Authorization": "Bearer " + b_tok})).json() assert cross["brand_name"] == "Beta Servicios" # b_tok can't see alpha async def test_widget_config_unknown_tenant_is_disabled_generic(app_client): """An unknown slug never leaks another tenant's branding AND is disabled (no fallback to default: a deleted client must get zero service).""" _app, client = app_client wc = (await client.get("/widget-config?t=does-not-exist")).json() assert wc["enabled"] is False assert wc["brand_name"] == "Asistente" and wc["starters"] == []