2026-10-08 22:35:00 +08:00
|
|
|
"""
|
2026-10-26 00:50:43 +08:00
|
|
|
Copyright 2024-2026 ReplyPier
|
2026-10-08 22:35:00 +08:00
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
from fastapi import FastAPI
|
|
|
|
|
from app.models.user import User, UserGroup
|
|
|
|
|
from app.models.chat_history import ChatHistory
|
|
|
|
|
from app.models.session_to_agent import SessionToAgent, SessionStatus
|
|
|
|
|
from app.models.agent import Agent
|
|
|
|
|
from app.models.role import Role
|
|
|
|
|
from app.models.customer import Customer
|
|
|
|
|
from app.models.permission import Permission, role_permissions
|
|
|
|
|
from uuid import uuid4
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
from app.api import chat as chat_router
|
|
|
|
|
from app.core.auth import get_current_user, require_permissions, get_unified_chat_auth
|
|
|
|
|
from app.database import get_db
|
|
|
|
|
from app.models.organization import Organization
|
|
|
|
|
|
|
|
|
|
# Create a test FastAPI app
|
|
|
|
|
app = FastAPI()
|
|
|
|
|
app.include_router(
|
|
|
|
|
chat_router.router,
|
|
|
|
|
prefix="/api/chats",
|
|
|
|
|
tags=["chat"]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_permissions(db) -> list[Permission]:
|
|
|
|
|
"""Create test permissions"""
|
|
|
|
|
permissions = []
|
|
|
|
|
for name in ["view_all_chats", "view_assigned_chats"]:
|
|
|
|
|
perm = Permission(
|
|
|
|
|
name=name,
|
|
|
|
|
description=f"Test permission for {name}"
|
|
|
|
|
)
|
|
|
|
|
db.add(perm)
|
|
|
|
|
permissions.append(perm)
|
|
|
|
|
db.commit()
|
|
|
|
|
for p in permissions:
|
|
|
|
|
db.refresh(p)
|
|
|
|
|
return permissions
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_role(db, test_organization_id, test_permissions) -> Role:
|
|
|
|
|
"""Create a test role with required permissions"""
|
|
|
|
|
role = Role(
|
|
|
|
|
name="Test Role",
|
|
|
|
|
description="Test Role Description",
|
|
|
|
|
organization_id=test_organization_id,
|
|
|
|
|
is_default=True
|
|
|
|
|
)
|
|
|
|
|
db.add(role)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
# Associate permissions with role
|
|
|
|
|
for perm in test_permissions:
|
|
|
|
|
db.execute(
|
|
|
|
|
role_permissions.insert().values(
|
|
|
|
|
role_id=role.id,
|
|
|
|
|
permission_id=perm.id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(role)
|
|
|
|
|
return role
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_user(db, test_organization_id, test_role) -> User:
|
|
|
|
|
"""Create a test user with required permissions"""
|
|
|
|
|
user = User(
|
|
|
|
|
id=uuid4(),
|
|
|
|
|
email="test@example.com",
|
|
|
|
|
hashed_password="hashed_password",
|
|
|
|
|
is_active=True,
|
|
|
|
|
organization_id=test_organization_id,
|
|
|
|
|
full_name="Test User",
|
|
|
|
|
role_id=test_role.id
|
|
|
|
|
)
|
|
|
|
|
db.add(user)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(user)
|
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_group(db, test_user) -> UserGroup:
|
|
|
|
|
"""Create a test user group"""
|
|
|
|
|
group = UserGroup(
|
|
|
|
|
id=uuid4(),
|
|
|
|
|
name="Test Group",
|
|
|
|
|
organization_id=test_user.organization_id
|
|
|
|
|
)
|
|
|
|
|
db.add(group)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(group)
|
|
|
|
|
return group
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_agent(db, test_user) -> Agent:
|
|
|
|
|
"""Create a test agent"""
|
|
|
|
|
agent = Agent(
|
|
|
|
|
id=uuid4(),
|
|
|
|
|
name="Test Agent",
|
|
|
|
|
display_name="Test Display Name",
|
|
|
|
|
description="Test Description",
|
|
|
|
|
agent_type="CUSTOMER_SUPPORT",
|
|
|
|
|
instructions=["Test instruction"],
|
|
|
|
|
is_active=True,
|
|
|
|
|
organization_id=test_user.organization_id
|
|
|
|
|
)
|
|
|
|
|
db.add(agent)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(agent)
|
|
|
|
|
return agent
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_customer(db, test_user) -> Customer:
|
|
|
|
|
"""Create a test customer"""
|
|
|
|
|
customer = Customer(
|
|
|
|
|
id=uuid4(),
|
|
|
|
|
email="customer@example.com",
|
|
|
|
|
full_name="Test Customer",
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
is_active=True
|
|
|
|
|
)
|
|
|
|
|
db.add(customer)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(customer)
|
|
|
|
|
return customer
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_chat_session(db, test_user, test_agent, test_customer) -> SessionToAgent:
|
|
|
|
|
"""Create a test chat session"""
|
|
|
|
|
session = SessionToAgent(
|
|
|
|
|
session_id=uuid4(),
|
|
|
|
|
user_id=test_user.id,
|
|
|
|
|
agent_id=test_agent.id,
|
|
|
|
|
customer_id=test_customer.id,
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
status=SessionStatus.OPEN,
|
|
|
|
|
assigned_at=datetime.now(timezone.utc)
|
|
|
|
|
)
|
|
|
|
|
db.add(session)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(session)
|
|
|
|
|
return session
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_chat_messages(db, test_chat_session, test_user, test_customer) -> list[ChatHistory]:
|
|
|
|
|
"""Create test chat messages"""
|
|
|
|
|
messages = []
|
|
|
|
|
for i in range(3):
|
|
|
|
|
message = ChatHistory(
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
user_id=test_user.id,
|
|
|
|
|
customer_id=test_customer.id,
|
|
|
|
|
agent_id=test_chat_session.agent_id,
|
|
|
|
|
session_id=test_chat_session.session_id,
|
|
|
|
|
message=f"Test message {i+1}",
|
|
|
|
|
message_type="user" if i % 2 == 0 else "bot",
|
|
|
|
|
created_at=datetime.now(timezone.utc)
|
|
|
|
|
)
|
|
|
|
|
messages.append(message)
|
|
|
|
|
db.add(message)
|
|
|
|
|
db.commit()
|
|
|
|
|
for m in messages:
|
|
|
|
|
db.refresh(m)
|
|
|
|
|
return messages
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def client(db, test_user) -> TestClient:
|
|
|
|
|
"""Create test client with mocked dependencies"""
|
|
|
|
|
async def override_get_current_user():
|
|
|
|
|
return test_user
|
|
|
|
|
|
|
|
|
|
async def override_require_permissions(*args, **kwargs):
|
|
|
|
|
return test_user
|
|
|
|
|
|
|
|
|
|
async def override_get_unified_chat_auth():
|
|
|
|
|
# Refresh the user to get the latest role and permissions from the database
|
|
|
|
|
db.refresh(test_user)
|
|
|
|
|
db.refresh(test_user.role)
|
|
|
|
|
|
|
|
|
|
# Get user permissions for chat auth
|
|
|
|
|
user_permissions = {p.name for p in test_user.role.permissions}
|
|
|
|
|
can_view_all = "view_all_chats" in user_permissions
|
|
|
|
|
can_view_assigned = "view_assigned_chats" in user_permissions
|
|
|
|
|
|
|
|
|
|
# Check if user has no permissions
|
|
|
|
|
if not (can_view_all or can_view_assigned):
|
|
|
|
|
from fastapi import HTTPException
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=403,
|
|
|
|
|
detail="Not enough permissions"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"auth_type": "jwt",
|
|
|
|
|
"organization_id": test_user.organization_id, # Keep as UUID
|
|
|
|
|
"user_id": test_user.id,
|
|
|
|
|
"current_user": test_user,
|
|
|
|
|
"can_view_all": can_view_all,
|
|
|
|
|
"can_view_assigned": can_view_assigned
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def override_get_db():
|
|
|
|
|
try:
|
|
|
|
|
yield db
|
|
|
|
|
finally:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
app.dependency_overrides[get_current_user] = override_get_current_user
|
|
|
|
|
app.dependency_overrides[require_permissions] = override_require_permissions
|
|
|
|
|
app.dependency_overrides[get_unified_chat_auth] = override_get_unified_chat_auth
|
|
|
|
|
app.dependency_overrides[get_db] = override_get_db
|
|
|
|
|
|
|
|
|
|
return TestClient(app)
|
|
|
|
|
|
|
|
|
|
# Test cases
|
|
|
|
|
def test_get_recent_chats_with_view_all(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session,
|
|
|
|
|
test_chat_messages
|
|
|
|
|
):
|
|
|
|
|
"""Test getting recent chats with view_all permission"""
|
|
|
|
|
response = client.get("/api/chats/recent")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chats = response.json()
|
|
|
|
|
assert len(chats) == 1
|
|
|
|
|
assert chats[0]["session_id"] == str(test_chat_session.session_id)
|
|
|
|
|
assert chats[0]["agent"]["id"] == str(test_chat_session.agent_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("status", ["null", "open,bogus", "OPEN"])
|
|
|
|
|
def test_get_recent_chats_refuses_an_unknown_status(client, test_user, status):
|
|
|
|
|
"""Compared with the status column, an unknown value was a database error (500)."""
|
|
|
|
|
response = client.get("/api/chats/recent", params={"status": status})
|
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
assert response.json()["detail"].startswith("Unknown status")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_refuses_an_offset_past_the_database_range(client, test_user):
|
|
|
|
|
"""Past a 64-bit OFFSET the query itself failed (500)."""
|
|
|
|
|
assert client.get("/api/chats/recent", params={"skip": 15412059487694436352}).status_code == 422
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("status", ["open", "open, transferred", "all"])
|
|
|
|
|
def test_get_recent_chats_accepts_known_statuses(client, test_user, test_chat_session, status):
|
|
|
|
|
assert client.get("/api/chats/recent", params={"status": status}).status_code == 200
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_includes_human_handler(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session,
|
|
|
|
|
test_chat_messages
|
|
|
|
|
):
|
|
|
|
|
"""A chat a human holds reports that human, so the inbox can label the row."""
|
|
|
|
|
response = client.get("/api/chats/recent")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chats = response.json()
|
|
|
|
|
# Three messages must still collapse into one row: the handler columns are
|
|
|
|
|
# functionally dependent on the session, so grouping cannot multiply rows.
|
|
|
|
|
assert len(chats) == 1
|
|
|
|
|
assert chats[0]["user_id"] == str(test_user.id)
|
|
|
|
|
assert chats[0]["user_name"] == test_user.full_name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_ai_handled_has_no_handler(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session,
|
|
|
|
|
test_chat_messages
|
|
|
|
|
):
|
|
|
|
|
"""No assignee means the AI still has it — both handler fields stay null."""
|
|
|
|
|
test_chat_session.user_id = None
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get("/api/chats/recent")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chats = response.json()
|
|
|
|
|
assert len(chats) == 1
|
|
|
|
|
assert chats[0]["user_id"] is None
|
|
|
|
|
assert chats[0]["user_name"] is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_reports_a_human_only_agent(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_agent,
|
|
|
|
|
test_chat_session,
|
|
|
|
|
test_chat_messages
|
|
|
|
|
):
|
|
|
|
|
"""The row needs the agent's flag to say "waiting" rather than "AI"."""
|
|
|
|
|
response = client.get("/api/chats/recent")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
assert response.json()[0]["agent"]["ai_replies_enabled"] is True
|
|
|
|
|
|
|
|
|
|
test_agent.ai_replies_enabled = False
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get("/api/chats/recent")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chats = response.json()
|
|
|
|
|
assert len(chats) == 1
|
|
|
|
|
assert chats[0]["agent"]["ai_replies_enabled"] is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_with_view_assigned(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session,
|
|
|
|
|
test_chat_messages
|
|
|
|
|
):
|
|
|
|
|
"""Test getting recent chats with view_assigned permission"""
|
|
|
|
|
# Remove view_all permission
|
|
|
|
|
view_all_perm = db.query(Permission).filter_by(name="view_all_chats").first()
|
|
|
|
|
db.execute(
|
|
|
|
|
role_permissions.delete().where(
|
|
|
|
|
role_permissions.c.permission_id == view_all_perm.id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get("/api/chats/recent")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chats = response.json()
|
|
|
|
|
assert len(chats) == 1 # Should only see own chat
|
|
|
|
|
assert chats[0]["session_id"] == str(test_chat_session.session_id)
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_no_permission(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session
|
|
|
|
|
):
|
|
|
|
|
"""Test getting recent chats without required permissions"""
|
|
|
|
|
# Remove all chat permissions
|
|
|
|
|
db.execute(role_permissions.delete())
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get("/api/chats/recent")
|
|
|
|
|
assert response.status_code == 403
|
|
|
|
|
assert response.json()["detail"] == "Not enough permissions"
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_success(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session,
|
|
|
|
|
test_chat_messages
|
|
|
|
|
):
|
|
|
|
|
"""Test getting chat detail successfully"""
|
|
|
|
|
response = client.get(f"/api/chats/{test_chat_session.session_id}")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chat = response.json()
|
|
|
|
|
assert chat["session_id"] == str(test_chat_session.session_id)
|
|
|
|
|
assert len(chat["messages"]) == 3
|
|
|
|
|
assert chat["agent"]["id"] == str(test_chat_session.agent_id)
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_not_found(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user
|
|
|
|
|
):
|
|
|
|
|
"""Test getting non-existent chat detail"""
|
|
|
|
|
response = client.get(f"/api/chats/{uuid4()}")
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
assert response.json()["detail"] == "Chat session not found"
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_wrong_org(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session
|
|
|
|
|
):
|
|
|
|
|
"""Test getting chat detail from wrong organization"""
|
|
|
|
|
# Create a new organization first
|
|
|
|
|
new_org = Organization(
|
|
|
|
|
id=uuid4(),
|
|
|
|
|
name="Test Org 2",
|
|
|
|
|
domain="test2.com",
|
|
|
|
|
timezone="UTC"
|
|
|
|
|
)
|
|
|
|
|
db.add(new_org)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
# Change session's organization
|
|
|
|
|
test_chat_session.organization_id = new_org.id
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(f"/api/chats/{test_chat_session.session_id}")
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
assert response.json()["detail"] == "Chat session not found"
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_no_permission(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session
|
|
|
|
|
):
|
|
|
|
|
"""Test getting chat detail without required permissions"""
|
|
|
|
|
# Remove all chat permissions
|
|
|
|
|
db.execute(role_permissions.delete())
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(f"/api/chats/{test_chat_session.session_id}")
|
|
|
|
|
assert response.status_code == 403
|
|
|
|
|
assert response.json()["detail"] == "Not enough permissions"
|
|
|
|
|
|
|
|
|
|
def test_get_chat_history_endpoint(client):
|
|
|
|
|
"""Test the basic chat history endpoint"""
|
|
|
|
|
response = client.get("/api/chats/")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
assert response.json() == {"message": "Chat history endpoint"}
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_with_filters(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_agent,
|
|
|
|
|
test_customer
|
|
|
|
|
):
|
|
|
|
|
"""Test getting recent chats with various filters"""
|
|
|
|
|
# Create multiple sessions with different statuses
|
|
|
|
|
open_session = SessionToAgent(
|
|
|
|
|
session_id=uuid4(),
|
|
|
|
|
user_id=test_user.id,
|
|
|
|
|
agent_id=test_agent.id,
|
|
|
|
|
customer_id=test_customer.id,
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
status=SessionStatus.OPEN,
|
|
|
|
|
assigned_at=datetime.now(timezone.utc)
|
|
|
|
|
)
|
|
|
|
|
db.add(open_session)
|
|
|
|
|
|
|
|
|
|
closed_session = SessionToAgent(
|
|
|
|
|
session_id=uuid4(),
|
|
|
|
|
user_id=test_user.id,
|
|
|
|
|
agent_id=test_agent.id,
|
|
|
|
|
customer_id=test_customer.id,
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
status=SessionStatus.CLOSED,
|
|
|
|
|
assigned_at=datetime.now(timezone.utc)
|
|
|
|
|
)
|
|
|
|
|
db.add(closed_session)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
# Test with status filter
|
|
|
|
|
response = client.get("/api/chats/recent?status=open")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chats = response.json()
|
|
|
|
|
assert all(chat["status"] == "OPEN" for chat in chats)
|
|
|
|
|
|
|
|
|
|
# Test with agent_id filter
|
|
|
|
|
response = client.get(f"/api/chats/recent?agent_id={test_agent.id}")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chats = response.json()
|
|
|
|
|
assert all(chat["agent"]["id"] == str(test_agent.id) for chat in chats)
|
|
|
|
|
|
|
|
|
|
# Test with limit and skip
|
|
|
|
|
response = client.get("/api/chats/recent?limit=1&skip=0")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chats = response.json()
|
|
|
|
|
assert len(chats) <= 1
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_invalid_uuid(client, db, test_user):
|
|
|
|
|
"""Test getting recent chats with invalid UUID format"""
|
|
|
|
|
response = client.get("/api/chats/recent?user_id=invalid-uuid")
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
assert "Invalid UUID format" in response.json()["detail"]
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_invalid_uuid(client, db, test_user):
|
|
|
|
|
"""Test getting chat detail with invalid session_id format"""
|
|
|
|
|
response = client.get("/api/chats/invalid-uuid")
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
assert "Invalid UUID format" in response.json()["detail"]
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_with_shopify_output(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session,
|
|
|
|
|
test_customer
|
|
|
|
|
):
|
|
|
|
|
"""Test getting chat detail with Shopify output in attributes"""
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
# Create message with shopify_output in attributes
|
|
|
|
|
shopify_data = {
|
|
|
|
|
"products": [{"id": "123", "title": "Test Product"}],
|
|
|
|
|
"search_query": "test"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
message = ChatHistory(
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
user_id=test_user.id,
|
|
|
|
|
customer_id=test_customer.id,
|
|
|
|
|
agent_id=test_chat_session.agent_id,
|
|
|
|
|
session_id=test_chat_session.session_id,
|
|
|
|
|
message="Here are some products",
|
|
|
|
|
message_type="bot",
|
|
|
|
|
attributes={
|
|
|
|
|
"shopify_output": shopify_data,
|
|
|
|
|
"end_chat": False
|
|
|
|
|
},
|
|
|
|
|
created_at=datetime.now(timezone.utc)
|
|
|
|
|
)
|
|
|
|
|
db.add(message)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(f"/api/chats/{test_chat_session.session_id}")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chat = response.json()
|
|
|
|
|
|
|
|
|
|
# Check that shopify_output was processed
|
|
|
|
|
messages = chat["messages"]
|
|
|
|
|
shopify_message = next((m for m in messages if m.get("shopify_output")), None)
|
|
|
|
|
assert shopify_message is not None
|
|
|
|
|
assert shopify_message["message_type"] == "product"
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_with_end_chat_reason(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session,
|
|
|
|
|
test_customer
|
|
|
|
|
):
|
|
|
|
|
"""Test getting chat detail with end_chat_reason in attributes"""
|
|
|
|
|
# Create message with valid end_chat_reason
|
|
|
|
|
message = ChatHistory(
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
user_id=test_user.id,
|
|
|
|
|
customer_id=test_customer.id,
|
|
|
|
|
agent_id=test_chat_session.agent_id,
|
|
|
|
|
session_id=test_chat_session.session_id,
|
|
|
|
|
message="Thank you!",
|
|
|
|
|
message_type="bot",
|
|
|
|
|
attributes={
|
|
|
|
|
"end_chat": True,
|
|
|
|
|
"end_chat_reason": "ISSUE_RESOLVED", # Valid enum value
|
|
|
|
|
"end_chat_description": "Issue resolved"
|
|
|
|
|
},
|
|
|
|
|
created_at=datetime.now(timezone.utc)
|
|
|
|
|
)
|
|
|
|
|
db.add(message)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(f"/api/chats/{test_chat_session.session_id}")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chat = response.json()
|
|
|
|
|
|
|
|
|
|
messages = chat["messages"]
|
|
|
|
|
end_chat_message = next((m for m in messages if m.get("end_chat")), None)
|
|
|
|
|
assert end_chat_message is not None
|
|
|
|
|
assert end_chat_message["end_chat_reason"] == "ISSUE_RESOLVED"
|
|
|
|
|
assert end_chat_message["end_chat_description"] == "Issue resolved"
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_with_invalid_end_chat_reason(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session,
|
|
|
|
|
test_customer
|
|
|
|
|
):
|
|
|
|
|
"""Test getting chat detail with invalid end_chat_reason (should be set to None)"""
|
|
|
|
|
# Create message with invalid end_chat_reason
|
|
|
|
|
message = ChatHistory(
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
user_id=test_user.id,
|
|
|
|
|
customer_id=test_customer.id,
|
|
|
|
|
agent_id=test_chat_session.agent_id,
|
|
|
|
|
session_id=test_chat_session.session_id,
|
|
|
|
|
message="Thank you!",
|
|
|
|
|
message_type="bot",
|
|
|
|
|
attributes={
|
|
|
|
|
"end_chat": True,
|
|
|
|
|
"end_chat_reason": "INVALID_REASON",
|
|
|
|
|
"end_chat_description": "Issue resolved"
|
|
|
|
|
},
|
|
|
|
|
created_at=datetime.now(timezone.utc)
|
|
|
|
|
)
|
|
|
|
|
db.add(message)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(f"/api/chats/{test_chat_session.session_id}")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chat = response.json()
|
|
|
|
|
|
|
|
|
|
messages = chat["messages"]
|
|
|
|
|
end_chat_message = next((m for m in messages if m.get("end_chat")), None)
|
|
|
|
|
assert end_chat_message is not None
|
|
|
|
|
# Invalid reason should be set to None
|
|
|
|
|
assert end_chat_message["end_chat_reason"] is None
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_assigned_access_denied(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_customer,
|
|
|
|
|
test_agent
|
|
|
|
|
):
|
|
|
|
|
"""Test that user with view_assigned permission cannot access unassigned chats"""
|
|
|
|
|
# Remove view_all permission
|
|
|
|
|
view_all_perm = db.query(Permission).filter_by(name="view_all_chats").first()
|
|
|
|
|
db.execute(
|
|
|
|
|
role_permissions.delete().where(
|
|
|
|
|
role_permissions.c.permission_id == view_all_perm.id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
# Create a session not assigned to test_user
|
|
|
|
|
other_user = User(
|
|
|
|
|
id=uuid4(),
|
|
|
|
|
email="other@example.com",
|
|
|
|
|
hashed_password="hashed",
|
|
|
|
|
is_active=True,
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
full_name="Other User",
|
|
|
|
|
role_id=test_user.role_id
|
|
|
|
|
)
|
|
|
|
|
db.add(other_user)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
other_session = SessionToAgent(
|
|
|
|
|
session_id=uuid4(),
|
|
|
|
|
user_id=other_user.id,
|
|
|
|
|
agent_id=test_agent.id,
|
|
|
|
|
customer_id=test_customer.id,
|
|
|
|
|
organization_id=test_user.organization_id,
|
|
|
|
|
status=SessionStatus.OPEN,
|
|
|
|
|
assigned_at=datetime.now(timezone.utc)
|
|
|
|
|
)
|
|
|
|
|
db.add(other_session)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(f"/api/chats/{other_session.session_id}")
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
assert response.json()["detail"] == "Chat session not found"
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_with_user_name_filter(
|
|
|
|
|
client,
|
|
|
|
|
db,
|
|
|
|
|
test_user,
|
|
|
|
|
test_chat_session
|
|
|
|
|
):
|
|
|
|
|
"""Test getting recent chats with user_name filter"""
|
|
|
|
|
# Test with user_name filter
|
|
|
|
|
response = client.get(f"/api/chats/recent?user_name={test_user.full_name}")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
# Filter parameters are passed to repository, endpoint should handle it
|
|
|
|
|
assert isinstance(response.json(), list)
|
|
|
|
|
|
|
|
|
|
def test_get_recent_chats_shopify_endpoint(client, db, test_user, test_chat_session):
|
|
|
|
|
"""Test the Shopify-specific endpoint"""
|
|
|
|
|
response = client.get("/api/chats/recent/shopify")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chats = response.json()
|
|
|
|
|
assert isinstance(chats, list)
|
|
|
|
|
|
|
|
|
|
def test_get_chat_detail_shopify_endpoint(client, db, test_user, test_chat_session, test_chat_messages):
|
|
|
|
|
"""Test the Shopify-specific chat detail endpoint"""
|
|
|
|
|
response = client.get(f"/api/chats/{test_chat_session.session_id}/shopify")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
chat = response.json()
|
|
|
|
|
assert chat["session_id"] == str(test_chat_session.session_id)
|