Files
DocsGPT/tests/api/user/test_conversations.py
T
81b6ee5daa Pg 4 (#2390)
* feat: postgres tests

* feat: mongo cutoff

* feat: mongo cutoff

* feat: adjust docs and compose files

* fix: mini code mongo removals

* fix: tests and k8s mongo stuff

* feat: test fixes

* fix: ruff

* fix: vale

* Potential fix for pull request finding 'CodeQL / Clear-text logging of sensitive information'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fix: mini suggestions

* vale lint fix 2

* fix: codeql columns thing

* fix: test mongo

* fix: tests coverage

* feat: better tests 4

* feat: more tests

* feat: decent coverage

* fix: ruff fixes

* fix: remove mongo mock

* feat: enhance workflow engine and API routes; add document retrieval and source handling

* feat: e2e tests

* fix: mcp, mongo and more

* fix: mini codeql warning

* fix: agent chunk view

* fix: mini issues

* fix: more pg fixes

* feat: postgres prep on start

* feat: qa tests

* fix: mini improvements

* fix: tests

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Siddhant Rai <siddhant.rai.5686@gmail.com>
2026-04-18 13:13:57 +01:00

630 lines
20 KiB
Python

import uuid
from contextlib import contextmanager
from unittest.mock import patch
import pytest
from flask import Flask
@pytest.fixture
def app():
app = Flask(__name__)
return app
@contextmanager
def _patch_conversations_db(conn):
@contextmanager
def _yield_conn():
yield conn
with patch(
"application.api.user.conversations.routes.db_session", _yield_conn
), patch(
"application.api.user.conversations.routes.db_readonly", _yield_conn
):
yield
def _seed_conversation(pg_conn, user_id, name="Test Conv"):
"""Create a conversation and return its PG uuid id as str."""
from application.storage.db.repositories.conversations import (
ConversationsRepository,
)
repo = ConversationsRepository(pg_conn)
conv = repo.create(user_id, name=name)
return str(conv["id"])
@pytest.mark.unit
class TestDeleteConversation:
pass
def test_returns_401_unauthenticated(self, app):
from application.api.user.conversations.routes import DeleteConversation
with app.test_request_context("/api/delete_conversation?id=abc"):
from flask import request
request.decoded_token = None
response = DeleteConversation().post()
assert response.status_code == 401
def test_returns_400_missing_id(self, app):
from application.api.user.conversations.routes import DeleteConversation
with app.test_request_context("/api/delete_conversation"):
from flask import request
request.decoded_token = {"sub": "user1"}
response = DeleteConversation().post()
assert response.status_code == 400
@pytest.mark.unit
class TestDeleteAllConversations:
pass
def test_returns_401_unauthenticated(self, app):
from application.api.user.conversations.routes import DeleteAllConversations
with app.test_request_context("/api/delete_all_conversations"):
from flask import request
request.decoded_token = None
response = DeleteAllConversations().get()
assert response.status_code == 401
@pytest.mark.unit
class TestGetConversations:
pass
def test_returns_401_unauthenticated(self, app):
from application.api.user.conversations.routes import GetConversations
with app.test_request_context("/api/get_conversations"):
from flask import request
request.decoded_token = None
response = GetConversations().get()
assert response.status_code == 401
@pytest.mark.unit
class TestGetSingleConversation:
pass
def test_returns_400_missing_id(self, app):
from application.api.user.conversations.routes import GetSingleConversation
with app.test_request_context("/api/get_single_conversation"):
from flask import request
request.decoded_token = {"sub": "user1"}
response = GetSingleConversation().get()
assert response.status_code == 400
@pytest.mark.unit
class TestUpdateConversationName:
pass
def test_returns_400_missing_fields(self, app):
from application.api.user.conversations.routes import UpdateConversationName
with app.test_request_context(
"/api/update_conversation_name",
method="POST",
json={"id": str(uuid.uuid4().hex[:24])},
):
from flask import request
request.decoded_token = {"sub": "user1"}
response = UpdateConversationName().post()
assert response.status_code == 400
@pytest.mark.unit
class TestSubmitFeedback:
pass
def test_returns_400_missing_fields(self, app):
from application.api.user.conversations.routes import SubmitFeedback
with app.test_request_context(
"/api/feedback",
method="POST",
json={"feedback": "LIKE"},
):
from flask import request
request.decoded_token = {"sub": "user1"}
response = SubmitFeedback().post()
assert response.status_code == 400
# ---------------------------------------------------------------------------
# Happy-path tests exercising real PG via the ephemeral pg_conn fixture.
# ---------------------------------------------------------------------------
class TestDeleteConversationHappy:
def test_deletes_existing_conversation(self, app, pg_conn):
from application.api.user.conversations.routes import DeleteConversation
from application.storage.db.repositories.conversations import (
ConversationsRepository,
)
user = "user-del"
conv_id = _seed_conversation(pg_conn, user)
with _patch_conversations_db(pg_conn), app.test_request_context(
f"/api/delete_conversation?id={conv_id}"
):
from flask import request
request.decoded_token = {"sub": user}
response = DeleteConversation().post()
assert response.status_code == 200
assert response.json["success"] is True
# Gone
assert ConversationsRepository(pg_conn).get_any(conv_id, user) is None
def test_delete_nonexistent_still_returns_200(self, app, pg_conn):
"""get_any returns None, so delete is a no-op but endpoint succeeds."""
from application.api.user.conversations.routes import DeleteConversation
with _patch_conversations_db(pg_conn), app.test_request_context(
f"/api/delete_conversation?id={uuid.uuid4()}"
):
from flask import request
request.decoded_token = {"sub": "u"}
response = DeleteConversation().post()
assert response.status_code == 200
def test_db_error_returns_400(self, app):
from application.api.user.conversations.routes import DeleteConversation
@contextmanager
def _broken():
raise RuntimeError("boom")
yield
with patch(
"application.api.user.conversations.routes.db_session", _broken
), app.test_request_context("/api/delete_conversation?id=abc"):
from flask import request
request.decoded_token = {"sub": "u"}
response = DeleteConversation().post()
assert response.status_code == 400
class TestDeleteAllConversationsHappy:
def test_deletes_all_conversations(self, app, pg_conn):
from application.api.user.conversations.routes import (
DeleteAllConversations,
)
from application.storage.db.repositories.conversations import (
ConversationsRepository,
)
user = "user-delall"
_seed_conversation(pg_conn, user, name="a")
_seed_conversation(pg_conn, user, name="b")
with _patch_conversations_db(pg_conn), app.test_request_context(
"/api/delete_all_conversations"
):
from flask import request
request.decoded_token = {"sub": user}
response = DeleteAllConversations().get()
assert response.status_code == 200
assert ConversationsRepository(pg_conn).list_for_user(user) == []
def test_db_error_returns_400(self, app):
from application.api.user.conversations.routes import (
DeleteAllConversations,
)
@contextmanager
def _broken():
raise RuntimeError("boom")
yield
with patch(
"application.api.user.conversations.routes.db_session", _broken
), app.test_request_context("/api/delete_all_conversations"):
from flask import request
request.decoded_token = {"sub": "u"}
response = DeleteAllConversations().get()
assert response.status_code == 400
class TestGetConversationsHappy:
def test_returns_list_of_conversations(self, app, pg_conn):
from application.api.user.conversations.routes import GetConversations
user = "user-list"
c1 = _seed_conversation(pg_conn, user, name="one")
c2 = _seed_conversation(pg_conn, user, name="two")
with _patch_conversations_db(pg_conn), app.test_request_context(
"/api/get_conversations"
):
from flask import request
request.decoded_token = {"sub": user}
response = GetConversations().get()
assert response.status_code == 200
ids = {c["id"] for c in response.json}
assert c1 in ids and c2 in ids
# agent_id, is_shared_usage, shared_token keys present
for c in response.json:
assert "agent_id" in c
assert "is_shared_usage" in c
assert "shared_token" in c
def test_db_error_returns_400(self, app):
from application.api.user.conversations.routes import GetConversations
@contextmanager
def _broken():
raise RuntimeError("boom")
yield
with patch(
"application.api.user.conversations.routes.db_readonly", _broken
), app.test_request_context("/api/get_conversations"):
from flask import request
request.decoded_token = {"sub": "u"}
response = GetConversations().get()
assert response.status_code == 400
class TestGetSingleConversationHappy:
def test_returns_401_unauthenticated(self, app):
from application.api.user.conversations.routes import (
GetSingleConversation,
)
with app.test_request_context("/api/get_single_conversation?id=x"):
from flask import request
request.decoded_token = None
response = GetSingleConversation().get()
assert response.status_code == 401
def test_returns_404_not_found(self, app, pg_conn):
from application.api.user.conversations.routes import (
GetSingleConversation,
)
with _patch_conversations_db(pg_conn), app.test_request_context(
f"/api/get_single_conversation?id={uuid.uuid4()}"
):
from flask import request
request.decoded_token = {"sub": "u"}
response = GetSingleConversation().get()
assert response.status_code == 404
def test_returns_conversation_with_messages(self, app, pg_conn):
from application.api.user.conversations.routes import (
GetSingleConversation,
)
from application.storage.db.repositories.conversations import (
ConversationsRepository,
)
user = "user-get"
conv_id = _seed_conversation(pg_conn, user, name="chat")
# Append a message
ConversationsRepository(pg_conn).append_message(
conv_id,
{
"prompt": "hi",
"response": "hello",
"thought": None,
"sources": [],
"tool_calls": [],
"timestamp": None,
"model_id": None,
},
)
with _patch_conversations_db(pg_conn), app.test_request_context(
f"/api/get_single_conversation?id={conv_id}"
):
from flask import request
request.decoded_token = {"sub": user}
response = GetSingleConversation().get()
assert response.status_code == 200
data = response.json
assert isinstance(data["queries"], list)
assert data["queries"][0]["prompt"] == "hi"
assert data["queries"][0]["response"] == "hello"
def test_returns_message_with_dict_feedback(self, app, pg_conn):
from application.api.user.conversations.routes import (
GetSingleConversation,
)
from application.storage.db.repositories.conversations import (
ConversationsRepository,
)
user = "user-fb"
conv_id = _seed_conversation(pg_conn, user, name="fb")
repo = ConversationsRepository(pg_conn)
repo.append_message(conv_id, {"prompt": "p", "response": "r"})
repo.set_feedback(
conv_id, 0, {"text": "like", "timestamp": "2024-01-01T00:00:00Z"}
)
with _patch_conversations_db(pg_conn), app.test_request_context(
f"/api/get_single_conversation?id={conv_id}"
):
from flask import request
request.decoded_token = {"sub": user}
response = GetSingleConversation().get()
assert response.status_code == 200
q = response.json["queries"][0]
assert q["feedback"] == "like"
assert q["feedback_timestamp"] == "2024-01-01T00:00:00Z"
def test_db_error_returns_400(self, app):
from application.api.user.conversations.routes import (
GetSingleConversation,
)
@contextmanager
def _broken():
raise RuntimeError("boom")
yield
with patch(
"application.api.user.conversations.routes.db_readonly", _broken
), app.test_request_context("/api/get_single_conversation?id=abc"):
from flask import request
request.decoded_token = {"sub": "u"}
response = GetSingleConversation().get()
assert response.status_code == 400
class TestUpdateConversationNameHappy:
def test_returns_401_unauthenticated(self, app):
from application.api.user.conversations.routes import (
UpdateConversationName,
)
with app.test_request_context(
"/api/update_conversation_name",
method="POST",
json={"id": "x", "name": "n"},
):
from flask import request
request.decoded_token = None
response = UpdateConversationName().post()
assert response.status_code == 401
def test_renames_conversation(self, app, pg_conn):
from application.api.user.conversations.routes import (
UpdateConversationName,
)
from application.storage.db.repositories.conversations import (
ConversationsRepository,
)
user = "user-rename"
conv_id = _seed_conversation(pg_conn, user, name="old")
with _patch_conversations_db(pg_conn), app.test_request_context(
"/api/update_conversation_name",
method="POST",
json={"id": conv_id, "name": "new"},
):
from flask import request
request.decoded_token = {"sub": user}
response = UpdateConversationName().post()
assert response.status_code == 200
got = ConversationsRepository(pg_conn).get_any(conv_id, user)
assert got["name"] == "new"
def test_rename_nonexistent_still_returns_200(self, app, pg_conn):
from application.api.user.conversations.routes import (
UpdateConversationName,
)
with _patch_conversations_db(pg_conn), app.test_request_context(
"/api/update_conversation_name",
method="POST",
json={"id": str(uuid.uuid4()), "name": "n"},
):
from flask import request
request.decoded_token = {"sub": "u"}
response = UpdateConversationName().post()
assert response.status_code == 200
def test_db_error_returns_400(self, app):
from application.api.user.conversations.routes import (
UpdateConversationName,
)
@contextmanager
def _broken():
raise RuntimeError("boom")
yield
with patch(
"application.api.user.conversations.routes.db_session", _broken
), app.test_request_context(
"/api/update_conversation_name",
method="POST",
json={"id": "x", "name": "n"},
):
from flask import request
request.decoded_token = {"sub": "u"}
response = UpdateConversationName().post()
assert response.status_code == 400
class TestSubmitFeedbackHappy:
def test_returns_401_unauthenticated(self, app):
from application.api.user.conversations.routes import SubmitFeedback
with app.test_request_context(
"/api/feedback",
method="POST",
json={
"feedback": "like",
"question_index": 0,
"conversation_id": "x",
},
):
from flask import request
request.decoded_token = None
response = SubmitFeedback().post()
assert response.status_code == 401
def test_submits_feedback(self, app, pg_conn):
from application.api.user.conversations.routes import SubmitFeedback
from application.storage.db.repositories.conversations import (
ConversationsRepository,
)
user = "user-fb1"
conv_id = _seed_conversation(pg_conn, user, name="fb")
ConversationsRepository(pg_conn).append_message(
conv_id, {"prompt": "p", "response": "r"}
)
with _patch_conversations_db(pg_conn), app.test_request_context(
"/api/feedback",
method="POST",
json={
"feedback": "LIKE", # uppercase normalized to lowercase
"question_index": 0,
"conversation_id": conv_id,
},
):
from flask import request
request.decoded_token = {"sub": user}
response = SubmitFeedback().post()
assert response.status_code == 200
msgs = ConversationsRepository(pg_conn).get_messages(conv_id)
fb = msgs[0].get("feedback")
assert fb and fb.get("text") == "like"
def test_none_feedback_allowed(self, app, pg_conn):
from application.api.user.conversations.routes import SubmitFeedback
from application.storage.db.repositories.conversations import (
ConversationsRepository,
)
user = "user-fb-none"
conv_id = _seed_conversation(pg_conn, user)
ConversationsRepository(pg_conn).append_message(
conv_id, {"prompt": "p", "response": "r"}
)
with _patch_conversations_db(pg_conn), app.test_request_context(
"/api/feedback",
method="POST",
json={
"feedback": None,
"question_index": 0,
"conversation_id": conv_id,
},
):
from flask import request
request.decoded_token = {"sub": user}
response = SubmitFeedback().post()
assert response.status_code == 200
def test_returns_404_for_missing_conversation(self, app, pg_conn):
from application.api.user.conversations.routes import SubmitFeedback
with _patch_conversations_db(pg_conn), app.test_request_context(
"/api/feedback",
method="POST",
json={
"feedback": "like",
"question_index": 0,
"conversation_id": str(uuid.uuid4()),
},
):
from flask import request
request.decoded_token = {"sub": "u"}
response = SubmitFeedback().post()
assert response.status_code == 404
def test_db_error_returns_400(self, app):
from application.api.user.conversations.routes import SubmitFeedback
@contextmanager
def _broken():
raise RuntimeError("boom")
yield
with patch(
"application.api.user.conversations.routes.db_session", _broken
), app.test_request_context(
"/api/feedback",
method="POST",
json={
"feedback": "like",
"question_index": 0,
"conversation_id": "x",
},
):
from flask import request
request.decoded_token = {"sub": "u"}
response = SubmitFeedback().post()
assert response.status_code == 400