emo-online-api / emo /backend /tests /backend_test.py
Emo Online
Deploy Emo API
dd87944
Raw
History Blame Contribute Delete
7.36 kB
"""Émo backend integration tests."""
import os
import time
import json
import uuid
import requests
import pytest
BASE_URL = os.environ.get('REACT_APP_BACKEND_URL', 'https://emo-personal.preview.emergentagent.com').rstrip('/')
API = f"{BASE_URL}/api"
UNIQUE = uuid.uuid4().hex[:8]
EMAIL = f"test_{UNIQUE}@example.com"
PASSWORD = "emo-test-2026"
NAME = f"Tester {UNIQUE}"
@pytest.fixture(scope="module")
def session():
s = requests.Session()
s.headers.update({"Content-Type": "application/json"})
return s
@pytest.fixture(scope="module")
def auth_session(session):
r = session.post(f"{API}/auth/signup", json={"email": EMAIL, "password": PASSWORD, "name": NAME})
assert r.status_code == 200, f"signup failed: {r.status_code} {r.text}"
data = r.json()
assert data["email"] == EMAIL
# Extract token from Set-Cookie (cookie is HttpOnly/secure/samesite=none — requests session has it)
return session
# ---------------- AUTH ---------------- #
def test_health(session):
r = session.get(f"{API}/")
assert r.status_code == 200
assert r.json().get("status") == "ok"
def test_signup_duplicate(auth_session):
r = auth_session.post(f"{API}/auth/signup", json={"email": EMAIL, "password": PASSWORD, "name": NAME})
assert r.status_code == 409
def test_me(auth_session):
r = auth_session.get(f"{API}/auth/me")
assert r.status_code == 200
data = r.json()
assert data["email"] == EMAIL
assert data["name"] == NAME
assert data["auth_provider"] == "password"
def test_login_wrong_password(session):
r = session.post(f"{API}/auth/login", json={"email": EMAIL, "password": "wrongpw"})
assert r.status_code == 401
def test_login_success_and_bearer_auth():
# Use new session, capture session_token cookie
s = requests.Session()
s.headers.update({"Content-Type": "application/json"})
r = s.post(f"{API}/auth/login", json={"email": EMAIL, "password": PASSWORD})
assert r.status_code == 200, r.text
token = s.cookies.get("session_token")
assert token, "session_token cookie not set"
# Verify Bearer auth works on a fresh session (no cookies)
s2 = requests.Session()
r2 = s2.get(f"{API}/auth/me", headers={"Authorization": f"Bearer {token}"})
assert r2.status_code == 200
assert r2.json()["email"] == EMAIL
def test_me_unauthenticated(session):
s = requests.Session()
r = s.get(f"{API}/auth/me")
assert r.status_code == 401
# ---------------- CONVERSATIONS ---------------- #
@pytest.fixture(scope="module")
def conversation_id(auth_session):
r = auth_session.post(f"{API}/conversations", json={"title": "TEST_conv", "mode": "normal"})
assert r.status_code == 200, r.text
data = r.json()
assert data["title"] == "TEST_conv"
assert data["mode"] == "normal"
assert "conversation_id" in data
assert "_id" not in data
return data["conversation_id"]
def test_list_conversations(auth_session, conversation_id):
r = auth_session.get(f"{API}/conversations")
assert r.status_code == 200
ids = [c["conversation_id"] for c in r.json()]
assert conversation_id in ids
def test_rename_conversation(auth_session, conversation_id):
r = auth_session.patch(f"{API}/conversations/{conversation_id}", json={"title": "TEST_renamed"})
assert r.status_code == 200
r2 = auth_session.get(f"{API}/conversations")
titles = {c["conversation_id"]: c["title"] for c in r2.json()}
assert titles[conversation_id] == "TEST_renamed"
def test_messages_empty(auth_session, conversation_id):
r = auth_session.get(f"{API}/conversations/{conversation_id}/messages")
assert r.status_code == 200
assert isinstance(r.json(), list)
def test_rename_not_found(auth_session):
r = auth_session.patch(f"{API}/conversations/conv_nonexistent", json={"title": "x"})
assert r.status_code == 404
# ---------------- CHAT STREAM ---------------- #
def _stream_chat(auth_session, conv_id, content, mode="normal"):
r = auth_session.post(
f"{API}/chat/stream",
json={"conversation_id": conv_id, "content": content, "mode": mode},
stream=True,
timeout=90,
)
assert r.status_code == 200, r.text
deltas = []
done = None
err = None
for line in r.iter_lines(decode_unicode=True):
if not line:
continue
if line.startswith("data: "):
payload = json.loads(line[6:])
if payload["type"] == "delta":
deltas.append(payload["content"])
elif payload["type"] == "done":
done = payload
elif payload["type"] == "error":
err = payload
return deltas, done, err
def test_chat_stream_normal_and_autotitle(auth_session):
# Fresh conversation to test auto-title
rc = auth_session.post(f"{API}/conversations", json={})
cid = rc.json()["conversation_id"]
deltas, done, err = _stream_chat(auth_session, cid, "Dis bonjour en une phrase.", "normal")
assert err is None, f"stream error: {err}"
assert len(deltas) > 0, "no deltas received"
full = "".join(deltas)
assert done is not None, "no done event"
assert done.get("mood"), "mood missing in done event"
# Persona: must not claim to be Claude
assert "claude" not in full.lower(), f"Persona leak: {full}"
# Auto-title set from first user message
convs = auth_session.get(f"{API}/conversations").json()
titles = {c["conversation_id"]: c["title"] for c in convs}
assert titles[cid] != "Nouvelle conversation", f"auto-title not set: {titles[cid]}"
# Messages persisted
msgs = auth_session.get(f"{API}/conversations/{cid}/messages").json()
assert len(msgs) >= 2
roles = [m["role"] for m in msgs]
assert "user" in roles and "emo" in roles
emo_msg = next(m for m in msgs if m["role"] == "emo")
assert "[MOOD:" not in emo_msg["content"], "MOOD tag not stripped from stored content"
assert emo_msg.get("mood") in (
"neutre", "amusee", "concentree", "sarcastique", "enthousiaste", "agacee", "curieuse", "pensive"
)
def test_chat_stream_tech_mode_persona(auth_session):
rc = auth_session.post(f"{API}/conversations", json={"mode": "tech"})
cid = rc.json()["conversation_id"]
deltas, done, err = _stream_chat(auth_session, cid, "Qui es-tu ?", "tech")
assert err is None
full = "".join(deltas).lower()
# Emo may say "pas Claude" (denying Claude identity) — that's fine.
# The real failure mode is claiming to BE Claude.
forbidden = ["je suis claude", "i am claude", "i'm claude", "modèle anthropic", "model anthropic"]
for phrase in forbidden:
assert phrase not in full, f"Emo claimed Claude identity: {full}"
assert "émo" in full or "emo" in full, f"Emo identity missing: {full}"
# ---------------- DELETE ---------------- #
def test_delete_conversation(auth_session, conversation_id):
r = auth_session.delete(f"{API}/conversations/{conversation_id}")
assert r.status_code == 200
# Verify gone
r2 = auth_session.get(f"{API}/conversations/{conversation_id}/messages")
assert r2.status_code == 404
def test_logout(auth_session):
r = auth_session.post(f"{API}/auth/logout")
assert r.status_code == 200
r2 = auth_session.get(f"{API}/auth/me")
assert r2.status_code == 401