Files
sales-trainer/backend/tests/test_session_repositories.py

166 lines
4.8 KiB
Python

"""Contract tests for the S4.3 session/message repository packet."""
from __future__ import annotations
from pathlib import Path
import pytest
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from app.db import Base, create_db_engine
from app.repositories import (
EntityNotFoundError,
SqlAlchemyGroupRepository,
SqlAlchemyOrganizationRepository,
SqlAlchemySessionRepository,
SqlAlchemyUserRepository,
)
@pytest.fixture
def repository_session(tmp_path: Path):
engine = create_db_engine(f"sqlite:///{tmp_path / 'sessions.db'}")
Base.metadata.create_all(engine)
with Session(engine) as session:
yield session
def _seed_training_context(repository_session: Session):
organizations = SqlAlchemyOrganizationRepository(repository_session)
users = SqlAlchemyUserRepository(repository_session)
groups = SqlAlchemyGroupRepository(repository_session)
organizations.create(organization_id="org-a", name="Alpha")
organizations.create(organization_id="org-b", name="Beta")
users.create(
user_id="user-a",
org_id="org-a",
username="alice",
password_hash="hash-a",
name="Alice",
role="user",
)
groups.create(group_id="group-a", org_id="org-a", name="Alpha Group")
groups.create(group_id="group-b", org_id="org-b", name="Beta Group")
groups.create_persona(
persona_id="persona-a",
group_id="group-a",
tier="base",
public_json={"name": "Customer A"},
)
repository_session.flush()
def test_session_repository_scopes_session_and_messages(repository_session: Session):
_seed_training_context(repository_session)
sessions = SqlAlchemySessionRepository(repository_session)
training = sessions.create(
session_id="session-a",
org_id="org-a",
user_id="user-a",
group_id="group-a",
persona_id="persona-a",
scenario_json={"channel": "social"},
)
sessions.append_message(
session_id="session-a",
org_id="org-a",
message_id="message-a",
sequence=1,
role="customer",
text="Hello",
)
sessions.append_message(
session_id="session-a",
org_id="org-a",
message_id="message-b",
sequence=2,
role="trainee",
text="Hi",
)
repository_session.commit()
assert sessions.get("session-a", org_id="org-a") is training
assert sessions.get("session-a", org_id="org-b") is None
assert [item.id for item in sessions.list_for_user("org-a", "user-a")] == ["session-a"]
assert [item.sequence for item in sessions.list_messages("session-a", org_id="org-a")] == [1, 2]
updated = sessions.update(
"session-a",
org_id="org-a",
status="completed",
outcome="won",
debrief_json={"score": 90},
)
repository_session.commit()
assert updated.status == "completed"
assert updated.outcome == "won"
with pytest.raises(EntityNotFoundError):
sessions.update("session-a", org_id="org-b", status="completed")
with pytest.raises(EntityNotFoundError):
sessions.append_message(
session_id="session-a",
org_id="org-b",
message_id="message-cross",
sequence=3,
role="trainee",
text="blocked",
)
def test_session_repository_preserves_one_shot_and_message_constraints(repository_session: Session):
_seed_training_context(repository_session)
sessions = SqlAlchemySessionRepository(repository_session)
sessions.create(
session_id="session-a",
org_id="org-a",
user_id="user-a",
group_id="group-a",
persona_id="persona-a",
mode="trainee",
)
repository_session.commit()
with pytest.raises(IntegrityError):
sessions.create(
session_id="session-b",
org_id="org-a",
user_id="user-a",
group_id="group-a",
persona_id="persona-a",
mode="trainee",
)
repository_session.flush()
repository_session.rollback()
sessions.create(
session_id="preview-b",
org_id="org-a",
user_id="user-a",
group_id="group-a",
persona_id="persona-a",
mode="preview",
)
repository_session.commit()
sessions.append_message(
session_id="preview-b",
org_id="org-a",
message_id="message-a",
sequence=1,
role="customer",
text="Hello",
)
with pytest.raises(IntegrityError):
sessions.append_message(
session_id="preview-b",
org_id="org-a",
message_id="message-b",
sequence=1,
role="trainee",
text="duplicate sequence",
)
repository_session.flush()
repository_session.rollback()