mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-15 20:15:30 +00:00
chore: more tests
This commit is contained in:
1 parent
dc6db847ca
commit
d5c0322e2a
64 files changed
+24436
-140
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,879 @@
|
||||
"""Tests for source chunk management routes."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from bson import ObjectId
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
def _status(response):
|
||||
if isinstance(response, tuple):
|
||||
return response[1]
|
||||
return response.status_code
|
||||
|
||||
|
||||
def _json(response):
|
||||
if isinstance(response, tuple):
|
||||
return response[0].json
|
||||
return response.json
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GetChunks
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetChunks:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
with app.test_request_context("/api/get_chunks?id=abc"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = GetChunks().get()
|
||||
|
||||
assert _status(response) == 401
|
||||
|
||||
def test_returns_400_for_invalid_doc_id(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
with app.test_request_context("/api/get_chunks?id=invalid"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetChunks().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
assert "Invalid doc_id" in _json(response)["error"]
|
||||
|
||||
def test_returns_404_when_doc_not_found(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(f"/api/get_chunks?id={doc_id}"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetChunks().get()
|
||||
|
||||
assert _status(response) == 404
|
||||
assert "not found" in _json(response)["error"]
|
||||
|
||||
def test_returns_paginated_chunks(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
|
||||
chunks = [
|
||||
{"text": f"chunk {i}", "metadata": {}, "doc_id": f"c{i}"}
|
||||
for i in range(25)
|
||||
]
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = chunks
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/get_chunks?id={doc_id}&page=2&per_page=10"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = GetChunks().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
data = _json(response)
|
||||
assert data["total"] == 25
|
||||
assert data["page"] == 2
|
||||
assert data["per_page"] == 10
|
||||
assert len(data["chunks"]) == 10
|
||||
|
||||
def test_filters_chunks_by_path(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
|
||||
chunks = [
|
||||
{"text": "a", "metadata": {"source": "inputs/dir/file.pdf"}, "doc_id": "c1"},
|
||||
{"text": "b", "metadata": {"source": "inputs/other.txt"}, "doc_id": "c2"},
|
||||
{"text": "c", "metadata": {"file_path": "guides/setup.md"}, "doc_id": "c3"},
|
||||
]
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = chunks
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/get_chunks?id={doc_id}&path=file.pdf"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = GetChunks().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data["total"] == 1
|
||||
assert data["chunks"][0]["text"] == "a"
|
||||
assert data["path"] == "file.pdf"
|
||||
|
||||
def test_filters_chunks_by_file_path_metadata(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
|
||||
chunks = [
|
||||
{"text": "a", "metadata": {"source": "inputs/dir/file.pdf"}, "doc_id": "c1"},
|
||||
{"text": "c", "metadata": {"file_path": "guides/setup.md"}, "doc_id": "c3"},
|
||||
]
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = chunks
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/get_chunks?id={doc_id}&path=setup.md"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = GetChunks().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data["total"] == 1
|
||||
assert data["chunks"][0]["text"] == "c"
|
||||
|
||||
def test_filters_chunks_by_search_term(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
|
||||
chunks = [
|
||||
{"text": "Python is great", "metadata": {"title": "intro"}, "doc_id": "c1"},
|
||||
{"text": "Java tutorial", "metadata": {"title": "java guide"}, "doc_id": "c2"},
|
||||
{"text": "Hello world", "metadata": {"title": "Python Basics"}, "doc_id": "c3"},
|
||||
]
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = chunks
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/get_chunks?id={doc_id}&search=python"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = GetChunks().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data["total"] == 2
|
||||
assert data["search"] == "python"
|
||||
|
||||
def test_combines_path_and_search_filters(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
|
||||
chunks = [
|
||||
{"text": "Python intro", "metadata": {"source": "dir/intro.md", "title": ""}, "doc_id": "c1"},
|
||||
{"text": "Python deep", "metadata": {"source": "dir/deep.md", "title": ""}, "doc_id": "c2"},
|
||||
{"text": "Java intro", "metadata": {"source": "dir/intro.md", "title": ""}, "doc_id": "c3"},
|
||||
]
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = chunks
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/get_chunks?id={doc_id}&path=intro.md&search=python"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = GetChunks().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data["total"] == 1
|
||||
assert data["chunks"][0]["doc_id"] == "c1"
|
||||
|
||||
def test_returns_500_on_store_error(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.side_effect = Exception("Store error")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(f"/api/get_chunks?id={doc_id}"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = GetChunks().get()
|
||||
|
||||
assert _status(response) == 500
|
||||
|
||||
def test_no_path_or_search_returns_null_fields(self, app):
|
||||
from application.api.user.sources.chunks import GetChunks
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = []
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(f"/api/get_chunks?id={doc_id}"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = GetChunks().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data["path"] is None
|
||||
assert data["search"] is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AddChunk
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAddChunk:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sources.chunks import AddChunk
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/add_chunk", method="POST", json={"id": "abc", "text": "hi"}
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = AddChunk().post()
|
||||
|
||||
assert _status(response) == 401
|
||||
|
||||
def test_returns_400_missing_required_fields(self, app):
|
||||
from application.api.user.sources.chunks import AddChunk
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/add_chunk", method="POST", json={"id": str(ObjectId())}
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = AddChunk().post()
|
||||
|
||||
# check_required_fields returns a tuple (response, status)
|
||||
assert response is not None
|
||||
|
||||
def test_returns_400_for_invalid_doc_id(self, app):
|
||||
from application.api.user.sources.chunks import AddChunk
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/add_chunk", method="POST", json={"id": "bad", "text": "hi"}
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = AddChunk().post()
|
||||
|
||||
assert _status(response) == 400
|
||||
assert "Invalid doc_id" in _json(response)["error"]
|
||||
|
||||
def test_returns_404_when_doc_not_found(self, app):
|
||||
from application.api.user.sources.chunks import AddChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/add_chunk", method="POST",
|
||||
json={"id": doc_id, "text": "hello"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = AddChunk().post()
|
||||
|
||||
assert _status(response) == 404
|
||||
|
||||
def test_adds_chunk_successfully(self, app):
|
||||
from application.api.user.sources.chunks import AddChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.add_chunk.return_value = "new-chunk-id"
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.num_tokens_from_string",
|
||||
return_value=5,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/add_chunk", method="POST",
|
||||
json={"id": doc_id, "text": "hello world"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = AddChunk().post()
|
||||
|
||||
assert _status(response) == 201
|
||||
data = _json(response)
|
||||
assert data["chunk_id"] == "new-chunk-id"
|
||||
assert "successfully" in data["message"]
|
||||
call_args = mock_store.add_chunk.call_args
|
||||
assert call_args[0][0] == "hello world"
|
||||
assert call_args[0][1]["token_count"] == 5
|
||||
|
||||
def test_adds_chunk_with_custom_metadata(self, app):
|
||||
from application.api.user.sources.chunks import AddChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.add_chunk.return_value = "cid"
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.num_tokens_from_string",
|
||||
return_value=3,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/add_chunk", method="POST",
|
||||
json={
|
||||
"id": doc_id,
|
||||
"text": "hi",
|
||||
"metadata": {"source": "test.pdf"},
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = AddChunk().post()
|
||||
|
||||
assert _status(response) == 201
|
||||
meta = mock_store.add_chunk.call_args[0][1]
|
||||
assert meta["source"] == "test.pdf"
|
||||
assert meta["token_count"] == 3
|
||||
|
||||
def test_returns_500_on_store_error(self, app):
|
||||
from application.api.user.sources.chunks import AddChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.add_chunk.side_effect = Exception("fail")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.num_tokens_from_string",
|
||||
return_value=1,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/add_chunk", method="POST",
|
||||
json={"id": doc_id, "text": "hello"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = AddChunk().post()
|
||||
|
||||
assert _status(response) == 500
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DeleteChunk
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDeleteChunk:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sources.chunks import DeleteChunk
|
||||
|
||||
with app.test_request_context("/api/delete_chunk?id=abc&chunk_id=xyz"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = DeleteChunk().delete()
|
||||
|
||||
assert _status(response) == 401
|
||||
|
||||
def test_returns_400_for_invalid_doc_id(self, app):
|
||||
from application.api.user.sources.chunks import DeleteChunk
|
||||
|
||||
with app.test_request_context("/api/delete_chunk?id=bad&chunk_id=xyz"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteChunk().delete()
|
||||
|
||||
assert _status(response) == 400
|
||||
assert "Invalid doc_id" in _json(response)["error"]
|
||||
|
||||
def test_returns_404_when_doc_not_found(self, app):
|
||||
from application.api.user.sources.chunks import DeleteChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/delete_chunk?id={doc_id}&chunk_id=cid"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteChunk().delete()
|
||||
|
||||
assert _status(response) == 404
|
||||
|
||||
def test_deletes_chunk_successfully(self, app):
|
||||
from application.api.user.sources.chunks import DeleteChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.delete_chunk.return_value = True
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/delete_chunk?id={doc_id}&chunk_id=cid"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteChunk().delete()
|
||||
|
||||
assert _status(response) == 200
|
||||
assert "successfully" in _json(response)["message"]
|
||||
mock_store.delete_chunk.assert_called_once_with("cid")
|
||||
|
||||
def test_returns_404_when_chunk_not_deleted(self, app):
|
||||
from application.api.user.sources.chunks import DeleteChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.delete_chunk.return_value = False
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/delete_chunk?id={doc_id}&chunk_id=missing"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteChunk().delete()
|
||||
|
||||
assert _status(response) == 404
|
||||
assert "not found" in _json(response)["message"]
|
||||
|
||||
def test_returns_500_on_store_error(self, app):
|
||||
from application.api.user.sources.chunks import DeleteChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.delete_chunk.side_effect = Exception("boom")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/delete_chunk?id={doc_id}&chunk_id=cid"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteChunk().delete()
|
||||
|
||||
assert _status(response) == 500
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# UpdateChunk
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestUpdateChunk:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT",
|
||||
json={"id": "abc", "chunk_id": "cid"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = UpdateChunk().put()
|
||||
|
||||
assert _status(response) == 401
|
||||
|
||||
def test_returns_400_missing_required_fields(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT", json={"id": str(ObjectId())}
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = UpdateChunk().put()
|
||||
|
||||
assert response is not None
|
||||
|
||||
def test_returns_400_for_invalid_doc_id(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT",
|
||||
json={"id": "bad", "chunk_id": "cid"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = UpdateChunk().put()
|
||||
|
||||
assert _status(response) == 400
|
||||
|
||||
def test_returns_404_when_doc_not_found(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT",
|
||||
json={"id": doc_id, "chunk_id": "cid"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = UpdateChunk().put()
|
||||
|
||||
assert _status(response) == 404
|
||||
|
||||
def test_returns_404_when_chunk_not_found(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = [
|
||||
{"doc_id": "other", "text": "x", "metadata": {}},
|
||||
]
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT",
|
||||
json={"id": doc_id, "chunk_id": "missing"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = UpdateChunk().put()
|
||||
|
||||
assert _status(response) == 404
|
||||
assert "Chunk not found" in _json(response)["error"]
|
||||
|
||||
def test_updates_chunk_text_successfully(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = [
|
||||
{"doc_id": "cid", "text": "old text", "metadata": {"source": "f.pdf"}},
|
||||
]
|
||||
mock_store.add_chunk.return_value = "new-cid"
|
||||
mock_store.delete_chunk.return_value = True
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.num_tokens_from_string",
|
||||
return_value=7,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT",
|
||||
json={"id": doc_id, "chunk_id": "cid", "text": "new text"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = UpdateChunk().put()
|
||||
|
||||
assert _status(response) == 200
|
||||
data = _json(response)
|
||||
assert data["chunk_id"] == "new-cid"
|
||||
assert data["original_chunk_id"] == "cid"
|
||||
# Verify add was called with new text and merged metadata
|
||||
add_call = mock_store.add_chunk.call_args
|
||||
assert add_call[0][0] == "new text"
|
||||
assert add_call[0][1]["source"] == "f.pdf"
|
||||
assert add_call[0][1]["token_count"] == 7
|
||||
mock_store.delete_chunk.assert_called_once_with("cid")
|
||||
|
||||
def test_updates_chunk_metadata_only(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = [
|
||||
{"doc_id": "cid", "text": "keep me", "metadata": {"source": "f.pdf"}},
|
||||
]
|
||||
mock_store.add_chunk.return_value = "new-cid"
|
||||
mock_store.delete_chunk.return_value = True
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT",
|
||||
json={
|
||||
"id": doc_id,
|
||||
"chunk_id": "cid",
|
||||
"metadata": {"title": "new title"},
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = UpdateChunk().put()
|
||||
|
||||
assert _status(response) == 200
|
||||
add_call = mock_store.add_chunk.call_args
|
||||
# text should be preserved
|
||||
assert add_call[0][0] == "keep me"
|
||||
# metadata should be merged
|
||||
assert add_call[0][1]["source"] == "f.pdf"
|
||||
assert add_call[0][1]["title"] == "new title"
|
||||
|
||||
def test_update_warns_when_old_chunk_delete_fails(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = [
|
||||
{"doc_id": "cid", "text": "text", "metadata": {}},
|
||||
]
|
||||
mock_store.add_chunk.return_value = "new-cid"
|
||||
mock_store.delete_chunk.return_value = False # delete fails
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT",
|
||||
json={"id": doc_id, "chunk_id": "cid"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = UpdateChunk().put()
|
||||
|
||||
# Still returns 200 with a warning logged
|
||||
assert _status(response) == 200
|
||||
|
||||
def test_returns_500_when_add_chunk_fails(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.return_value = [
|
||||
{"doc_id": "cid", "text": "text", "metadata": {}},
|
||||
]
|
||||
mock_store.add_chunk.side_effect = Exception("add failed")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT",
|
||||
json={"id": doc_id, "chunk_id": "cid", "text": "new"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = UpdateChunk().put()
|
||||
|
||||
assert _status(response) == 500
|
||||
assert "addition failed" in _json(response)["error"]
|
||||
|
||||
def test_returns_500_on_general_store_error(self, app):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {"_id": ObjectId(doc_id), "user": "u1"}
|
||||
mock_store = Mock()
|
||||
mock_store.get_chunks.side_effect = Exception("connection lost")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.chunks.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.chunks.get_vector_store",
|
||||
return_value=mock_store,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/update_chunk", method="PUT",
|
||||
json={"id": doc_id, "chunk_id": "cid"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = UpdateChunk().put()
|
||||
|
||||
assert _status(response) == 500
|
||||
@@ -0,0 +1,965 @@
|
||||
"""Tests for source management routes (CombinedJson, PaginatedSources,
|
||||
DeleteByIds, DeleteOldIndexes, ManageSync, DirectoryStructure).
|
||||
|
||||
Note: SyncSource and _get_provider_from_remote_data are already covered in
|
||||
test_routes.py and are NOT duplicated here.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
from bson import ObjectId
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
def _status(response):
|
||||
if isinstance(response, tuple):
|
||||
return response[1]
|
||||
return response.status_code
|
||||
|
||||
|
||||
def _json(response):
|
||||
if isinstance(response, tuple):
|
||||
return response[0].json
|
||||
return response.json
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CombinedJson (/api/sources)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestCombinedJson:
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sources.routes import CombinedJson
|
||||
|
||||
with app.test_request_context("/api/sources"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = CombinedJson().get()
|
||||
|
||||
assert _status(response) == 401
|
||||
|
||||
def test_returns_default_source_plus_user_sources(self, app):
|
||||
from application.api.user.sources.routes import CombinedJson
|
||||
|
||||
src_id = ObjectId()
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.sort.return_value = [
|
||||
{
|
||||
"_id": src_id,
|
||||
"name": "My Doc",
|
||||
"date": "2024-01-01",
|
||||
"tokens": "100",
|
||||
"retriever": "classic",
|
||||
"sync_frequency": "daily",
|
||||
"remote_data": json.dumps({"provider": "github"}),
|
||||
"directory_structure": None,
|
||||
"type": "file",
|
||||
}
|
||||
]
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = mock_cursor
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/sources"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = CombinedJson().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
data = _json(response)
|
||||
# First entry is always the Default
|
||||
assert data[0]["name"] == "Default"
|
||||
assert data[0]["date"] == "default"
|
||||
# Second entry is user source
|
||||
assert data[1]["id"] == str(src_id)
|
||||
assert data[1]["name"] == "My Doc"
|
||||
assert data[1]["provider"] == "github"
|
||||
assert data[1]["syncFrequency"] == "daily"
|
||||
assert data[1]["is_nested"] is False
|
||||
|
||||
def test_is_nested_true_when_directory_structure_present(self, app):
|
||||
from application.api.user.sources.routes import CombinedJson
|
||||
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.sort.return_value = [
|
||||
{
|
||||
"_id": ObjectId(),
|
||||
"name": "Nested",
|
||||
"date": "2024-01-01",
|
||||
"directory_structure": {"files": ["a.txt"]},
|
||||
}
|
||||
]
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = mock_cursor
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/sources"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = CombinedJson().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data[1]["is_nested"] is True
|
||||
|
||||
def test_returns_400_on_db_error(self, app):
|
||||
from application.api.user.sources.routes import CombinedJson
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.side_effect = Exception("db err")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/sources"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = CombinedJson().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
|
||||
def test_type_defaults_to_file(self, app):
|
||||
from application.api.user.sources.routes import CombinedJson
|
||||
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.sort.return_value = [
|
||||
{"_id": ObjectId(), "name": "X", "date": "d"}
|
||||
]
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = mock_cursor
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/sources"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = CombinedJson().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data[1]["type"] == "file"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PaginatedSources (/api/sources/paginated)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestPaginatedSources:
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sources.routes import PaginatedSources
|
||||
|
||||
with app.test_request_context("/api/sources/paginated"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = PaginatedSources().get()
|
||||
|
||||
assert _status(response) == 401
|
||||
|
||||
def test_returns_paginated_results(self, app):
|
||||
from application.api.user.sources.routes import PaginatedSources
|
||||
|
||||
ids = [ObjectId() for _ in range(3)]
|
||||
docs = [
|
||||
{"_id": ids[i], "name": f"Doc{i}", "date": f"2024-0{i + 1}-01"}
|
||||
for i in range(3)
|
||||
]
|
||||
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.sort.return_value = mock_cursor
|
||||
mock_cursor.skip.return_value = mock_cursor
|
||||
mock_cursor.limit.return_value = docs
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = mock_cursor
|
||||
mock_collection.count_documents.return_value = 3
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/sources/paginated?page=1&rows=10&sort=date&order=desc"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = PaginatedSources().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
data = _json(response)
|
||||
assert data["total"] == 3
|
||||
assert data["totalPages"] == 1
|
||||
assert data["currentPage"] == 1
|
||||
assert len(data["paginated"]) == 3
|
||||
|
||||
def test_search_filter_applies_regex(self, app):
|
||||
from application.api.user.sources.routes import PaginatedSources
|
||||
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.sort.return_value = mock_cursor
|
||||
mock_cursor.skip.return_value = mock_cursor
|
||||
mock_cursor.limit.return_value = []
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = mock_cursor
|
||||
mock_collection.count_documents.return_value = 0
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/sources/paginated?search=test%20doc"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = PaginatedSources().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
# Verify search query was passed
|
||||
query_arg = mock_collection.count_documents.call_args[0][0]
|
||||
assert query_arg["name"]["$regex"] == "test doc"
|
||||
assert query_arg["name"]["$options"] == "i"
|
||||
|
||||
def test_ascending_sort_order(self, app):
|
||||
from application.api.user.sources.routes import PaginatedSources
|
||||
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.sort.return_value = mock_cursor
|
||||
mock_cursor.skip.return_value = mock_cursor
|
||||
mock_cursor.limit.return_value = []
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = mock_cursor
|
||||
mock_collection.count_documents.return_value = 0
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/sources/paginated?order=asc&sort=name"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = PaginatedSources().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
mock_cursor.sort.assert_called_once_with("name", 1)
|
||||
|
||||
def test_page_clamped_to_valid_range(self, app):
|
||||
from application.api.user.sources.routes import PaginatedSources
|
||||
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.sort.return_value = mock_cursor
|
||||
mock_cursor.skip.return_value = mock_cursor
|
||||
mock_cursor.limit.return_value = []
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = mock_cursor
|
||||
mock_collection.count_documents.return_value = 5 # 1 page with default 10 rows
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/sources/paginated?page=999"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = PaginatedSources().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data["currentPage"] == 1 # clamped
|
||||
|
||||
def test_returns_400_on_db_error(self, app):
|
||||
from application.api.user.sources.routes import PaginatedSources
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.count_documents.return_value = 0
|
||||
mock_collection.find.side_effect = Exception("db error")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/sources/paginated"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = PaginatedSources().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
|
||||
def test_paginated_includes_provider_and_is_nested(self, app):
|
||||
from application.api.user.sources.routes import PaginatedSources
|
||||
|
||||
doc = {
|
||||
"_id": ObjectId(),
|
||||
"name": "S3 Src",
|
||||
"date": "2024-01-01",
|
||||
"remote_data": {"provider": "s3"},
|
||||
"directory_structure": {"dirs": ["a"]},
|
||||
"type": "s3",
|
||||
}
|
||||
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.sort.return_value = mock_cursor
|
||||
mock_cursor.skip.return_value = mock_cursor
|
||||
mock_cursor.limit.return_value = [doc]
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = mock_cursor
|
||||
mock_collection.count_documents.return_value = 1
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/sources/paginated"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = PaginatedSources().get()
|
||||
|
||||
data = _json(response)
|
||||
entry = data["paginated"][0]
|
||||
assert entry["provider"] == "s3"
|
||||
assert entry["isNested"] is True
|
||||
assert entry["type"] == "s3"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DeleteByIds (/api/delete_by_ids)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDeleteByIds:
|
||||
|
||||
def test_returns_400_when_path_missing(self, app):
|
||||
from application.api.user.sources.routes import DeleteByIds
|
||||
|
||||
with app.test_request_context("/api/delete_by_ids"):
|
||||
response = DeleteByIds().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
assert "Missing" in _json(response)["message"]
|
||||
|
||||
def test_returns_200_on_successful_delete(self, app):
|
||||
from application.api.user.sources.routes import DeleteByIds
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.delete_index.return_value = True
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/delete_by_ids?path=id1,id2"):
|
||||
response = DeleteByIds().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
assert _json(response)["success"] is True
|
||||
mock_collection.delete_index.assert_called_once_with(ids="id1,id2")
|
||||
|
||||
def test_returns_400_when_delete_returns_false(self, app):
|
||||
from application.api.user.sources.routes import DeleteByIds
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.delete_index.return_value = False
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/delete_by_ids?path=id1"):
|
||||
response = DeleteByIds().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
|
||||
def test_returns_400_on_exception(self, app):
|
||||
from application.api.user.sources.routes import DeleteByIds
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.delete_index.side_effect = Exception("fail")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/delete_by_ids?path=id1"):
|
||||
response = DeleteByIds().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DeleteOldIndexes (/api/delete_old)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDeleteOldIndexes:
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sources.routes import DeleteOldIndexes
|
||||
|
||||
with app.test_request_context("/api/delete_old?source_id=abc"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = DeleteOldIndexes().get()
|
||||
|
||||
assert _status(response) == 401
|
||||
|
||||
def test_returns_400_when_source_id_missing(self, app):
|
||||
from application.api.user.sources.routes import DeleteOldIndexes
|
||||
|
||||
with app.test_request_context("/api/delete_old"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteOldIndexes().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
|
||||
def test_returns_404_when_doc_not_found(self, app):
|
||||
from application.api.user.sources.routes import DeleteOldIndexes
|
||||
|
||||
source_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(f"/api/delete_old?source_id={source_id}"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteOldIndexes().get()
|
||||
|
||||
assert _status(response) == 404
|
||||
|
||||
def test_deletes_faiss_index_and_file(self, app):
|
||||
from application.api.user.sources.routes import DeleteOldIndexes
|
||||
|
||||
source_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": source_id,
|
||||
"user": "u1",
|
||||
"file_path": "uploads/u1/doc.pdf",
|
||||
}
|
||||
mock_storage = Mock()
|
||||
mock_storage.file_exists.return_value = True
|
||||
mock_storage.is_directory.return_value = False
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.StorageCreator.get_storage",
|
||||
return_value=mock_storage,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.settings"
|
||||
) as mock_settings:
|
||||
mock_settings.VECTOR_STORE = "faiss"
|
||||
with app.test_request_context(
|
||||
f"/api/delete_old?source_id={source_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteOldIndexes().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
assert _json(response)["success"] is True
|
||||
# Should have checked and deleted faiss files
|
||||
assert mock_storage.delete_file.call_count >= 1
|
||||
mock_collection.delete_one.assert_called_once()
|
||||
|
||||
def test_deletes_non_faiss_vector_index(self, app):
|
||||
from application.api.user.sources.routes import DeleteOldIndexes
|
||||
|
||||
source_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": source_id,
|
||||
"user": "u1",
|
||||
}
|
||||
mock_storage = Mock()
|
||||
mock_vectorstore = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.StorageCreator.get_storage",
|
||||
return_value=mock_storage,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.VectorCreator.create_vectorstore",
|
||||
return_value=mock_vectorstore,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.settings"
|
||||
) as mock_settings:
|
||||
mock_settings.VECTOR_STORE = "elasticsearch"
|
||||
with app.test_request_context(
|
||||
f"/api/delete_old?source_id={source_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteOldIndexes().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
mock_vectorstore.delete_index.assert_called_once()
|
||||
|
||||
def test_deletes_directory_of_files(self, app):
|
||||
from application.api.user.sources.routes import DeleteOldIndexes
|
||||
|
||||
source_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": source_id,
|
||||
"user": "u1",
|
||||
"file_path": "uploads/u1/mydir",
|
||||
}
|
||||
mock_storage = Mock()
|
||||
mock_storage.is_directory.return_value = True
|
||||
mock_storage.list_files.return_value = ["uploads/u1/mydir/a.txt", "uploads/u1/mydir/b.txt"]
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.StorageCreator.get_storage",
|
||||
return_value=mock_storage,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.settings"
|
||||
) as mock_settings:
|
||||
mock_settings.VECTOR_STORE = "faiss"
|
||||
mock_storage.file_exists.return_value = False
|
||||
with app.test_request_context(
|
||||
f"/api/delete_old?source_id={source_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteOldIndexes().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
# Each file in directory should be deleted
|
||||
assert mock_storage.delete_file.call_count == 2
|
||||
|
||||
def test_handles_file_not_found_gracefully(self, app):
|
||||
from application.api.user.sources.routes import DeleteOldIndexes
|
||||
|
||||
source_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": source_id,
|
||||
"user": "u1",
|
||||
"file_path": "uploads/missing.pdf",
|
||||
}
|
||||
mock_storage = Mock()
|
||||
mock_storage.is_directory.side_effect = FileNotFoundError()
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.StorageCreator.get_storage",
|
||||
return_value=mock_storage,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.settings"
|
||||
) as mock_settings:
|
||||
mock_settings.VECTOR_STORE = "faiss"
|
||||
mock_storage.file_exists.return_value = False
|
||||
with app.test_request_context(
|
||||
f"/api/delete_old?source_id={source_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteOldIndexes().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
mock_collection.delete_one.assert_called_once()
|
||||
|
||||
def test_returns_400_on_general_error(self, app):
|
||||
from application.api.user.sources.routes import DeleteOldIndexes
|
||||
|
||||
source_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": source_id,
|
||||
"user": "u1",
|
||||
}
|
||||
mock_storage = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.StorageCreator.get_storage",
|
||||
return_value=mock_storage,
|
||||
), patch(
|
||||
"application.api.user.sources.routes.settings"
|
||||
) as mock_settings:
|
||||
mock_settings.VECTOR_STORE = "faiss"
|
||||
mock_storage.file_exists.side_effect = RuntimeError("disk error")
|
||||
with app.test_request_context(
|
||||
f"/api/delete_old?source_id={source_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DeleteOldIndexes().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ManageSync (/api/manage_sync)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestManageSync:
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sources.routes import ManageSync
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/manage_sync", method="POST",
|
||||
json={"source_id": "x", "sync_frequency": "daily"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = ManageSync().post()
|
||||
|
||||
assert _status(response) == 401
|
||||
|
||||
def test_returns_400_missing_fields(self, app):
|
||||
from application.api.user.sources.routes import ManageSync
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/manage_sync", method="POST",
|
||||
json={"source_id": "abc"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = ManageSync().post()
|
||||
|
||||
assert response is not None
|
||||
|
||||
def test_returns_400_for_invalid_frequency(self, app):
|
||||
from application.api.user.sources.routes import ManageSync
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/manage_sync", method="POST",
|
||||
json={"source_id": str(ObjectId()), "sync_frequency": "hourly"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = ManageSync().post()
|
||||
|
||||
assert _status(response) == 400
|
||||
assert "Invalid frequency" in _json(response)["message"]
|
||||
|
||||
def test_updates_sync_frequency_successfully(self, app):
|
||||
from application.api.user.sources.routes import ManageSync
|
||||
|
||||
source_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/manage_sync", method="POST",
|
||||
json={"source_id": source_id, "sync_frequency": "weekly"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = ManageSync().post()
|
||||
|
||||
assert _status(response) == 200
|
||||
assert _json(response)["success"] is True
|
||||
call_args = mock_collection.update_one.call_args
|
||||
assert call_args[0][0]["_id"] == ObjectId(source_id)
|
||||
assert call_args[0][0]["user"] == "u1"
|
||||
assert call_args[0][1]["$set"]["sync_frequency"] == "weekly"
|
||||
|
||||
def test_accepts_all_valid_frequencies(self, app):
|
||||
from application.api.user.sources.routes import ManageSync
|
||||
|
||||
mock_collection = Mock()
|
||||
|
||||
for freq in ["never", "daily", "weekly", "monthly"]:
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/manage_sync", method="POST",
|
||||
json={"source_id": str(ObjectId()), "sync_frequency": freq},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = ManageSync().post()
|
||||
|
||||
assert _status(response) == 200
|
||||
|
||||
def test_returns_400_on_db_error(self, app):
|
||||
from application.api.user.sources.routes import ManageSync
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.update_one.side_effect = Exception("db err")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/manage_sync", method="POST",
|
||||
json={"source_id": str(ObjectId()), "sync_frequency": "daily"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = ManageSync().post()
|
||||
|
||||
assert _status(response) == 400
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# RedirectToSources (/api/combine)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestRedirectToSources:
|
||||
|
||||
def test_redirects_to_sources(self, app):
|
||||
from application.api.user.sources.routes import RedirectToSources
|
||||
|
||||
with app.test_request_context("/api/combine"):
|
||||
response = RedirectToSources().get()
|
||||
|
||||
assert response.status_code == 301
|
||||
assert response.location == "/api/sources"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DirectoryStructure (/api/directory_structure)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDirectoryStructure:
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sources.routes import DirectoryStructure
|
||||
|
||||
with app.test_request_context("/api/directory_structure?id=abc"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = DirectoryStructure().get()
|
||||
|
||||
assert _status(response) == 401
|
||||
|
||||
def test_returns_400_when_id_missing(self, app):
|
||||
from application.api.user.sources.routes import DirectoryStructure
|
||||
|
||||
with app.test_request_context("/api/directory_structure"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DirectoryStructure().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
assert "required" in _json(response)["error"]
|
||||
|
||||
def test_returns_400_for_invalid_doc_id(self, app):
|
||||
from application.api.user.sources.routes import DirectoryStructure
|
||||
|
||||
with app.test_request_context("/api/directory_structure?id=invalid"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DirectoryStructure().get()
|
||||
|
||||
assert _status(response) == 400
|
||||
assert "Invalid" in _json(response)["error"]
|
||||
|
||||
def test_returns_404_when_doc_not_found(self, app):
|
||||
from application.api.user.sources.routes import DirectoryStructure
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(f"/api/directory_structure?id={doc_id}"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DirectoryStructure().get()
|
||||
|
||||
assert _status(response) == 404
|
||||
assert "not found" in _json(response)["error"]
|
||||
|
||||
def test_returns_directory_structure(self, app):
|
||||
from application.api.user.sources.routes import DirectoryStructure
|
||||
|
||||
doc_id = ObjectId()
|
||||
dir_struct = {"dirs": ["a", "b"], "files": ["c.txt"]}
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": doc_id,
|
||||
"user": "u1",
|
||||
"directory_structure": dir_struct,
|
||||
"file_path": "uploads/u1/mydir",
|
||||
"remote_data": json.dumps({"provider": "github"}),
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/directory_structure?id={doc_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DirectoryStructure().get()
|
||||
|
||||
assert _status(response) == 200
|
||||
data = _json(response)
|
||||
assert data["success"] is True
|
||||
assert data["directory_structure"] == dir_struct
|
||||
assert data["base_path"] == "uploads/u1/mydir"
|
||||
assert data["provider"] == "github"
|
||||
|
||||
def test_returns_none_provider_when_no_remote_data(self, app):
|
||||
from application.api.user.sources.routes import DirectoryStructure
|
||||
|
||||
doc_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": doc_id,
|
||||
"user": "u1",
|
||||
"directory_structure": {},
|
||||
"file_path": "path",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/directory_structure?id={doc_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DirectoryStructure().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data["provider"] is None
|
||||
|
||||
def test_handles_invalid_remote_data_json(self, app):
|
||||
from application.api.user.sources.routes import DirectoryStructure
|
||||
|
||||
doc_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": doc_id,
|
||||
"user": "u1",
|
||||
"directory_structure": {},
|
||||
"file_path": "path",
|
||||
"remote_data": "not-valid-json{",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/directory_structure?id={doc_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DirectoryStructure().get()
|
||||
|
||||
data = _json(response)
|
||||
assert data["success"] is True
|
||||
assert data["provider"] is None
|
||||
|
||||
def test_returns_500_on_general_error(self, app):
|
||||
from application.api.user.sources.routes import DirectoryStructure
|
||||
|
||||
doc_id = str(ObjectId())
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.side_effect = Exception("db error")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sources.routes.sources_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/directory_structure?id={doc_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "u1"}
|
||||
response = DirectoryStructure().get()
|
||||
|
||||
assert _status(response) == 500
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,768 @@
|
||||
"""Tests for application.api.user.agents.sharing module."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from bson import DBRef, ObjectId
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SharedAgent (GET /shared_agent)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSharedAgent:
|
||||
|
||||
def test_returns_400_missing_token(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
with app.test_request_context("/api/shared_agent"):
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_agent_not_found(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
mock_col = Mock()
|
||||
mock_col.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_col
|
||||
):
|
||||
with app.test_request_context("/api/shared_agent?token=abc123"):
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_returns_shared_agent_data(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "owner1",
|
||||
"name": "Shared Agent",
|
||||
"description": "A shared agent",
|
||||
"chunks": "5",
|
||||
"retriever": "classic",
|
||||
"prompt_id": "default",
|
||||
"tools": [],
|
||||
"agent_type": "classic",
|
||||
"status": "published",
|
||||
"shared_publicly": True,
|
||||
"shared_token": "abc123",
|
||||
}
|
||||
mock_resolve = Mock(return_value=[])
|
||||
mock_db = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.resolve_tool_details", mock_resolve
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.db", mock_db
|
||||
):
|
||||
with app.test_request_context("/api/shared_agent?token=abc123"):
|
||||
from flask import request
|
||||
|
||||
# No decoded_token -> anonymous access
|
||||
request.decoded_token = None
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
assert data["id"] == str(agent_id)
|
||||
assert data["name"] == "Shared Agent"
|
||||
assert data["shared"] is True
|
||||
|
||||
def test_adds_to_shared_with_me_for_different_user(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "owner1",
|
||||
"name": "Agent",
|
||||
"tools": [],
|
||||
"shared_publicly": True,
|
||||
"shared_token": "abc123",
|
||||
}
|
||||
mock_resolve = Mock(return_value=[])
|
||||
mock_db = Mock()
|
||||
mock_ensure = Mock(return_value={"user_id": "user2"})
|
||||
mock_users_col = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.resolve_tool_details", mock_resolve
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.db", mock_db
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.ensure_user_doc", mock_ensure
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.users_collection", mock_users_col
|
||||
):
|
||||
with app.test_request_context("/api/shared_agent?token=abc123"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user2"}
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 200
|
||||
mock_ensure.assert_called_once_with("user2")
|
||||
mock_users_col.update_one.assert_called_once()
|
||||
|
||||
def test_does_not_add_to_shared_for_owner(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "owner1",
|
||||
"name": "Agent",
|
||||
"tools": [],
|
||||
"shared_publicly": True,
|
||||
"shared_token": "abc123",
|
||||
}
|
||||
mock_resolve = Mock(return_value=[])
|
||||
mock_db = Mock()
|
||||
mock_ensure = Mock()
|
||||
mock_users_col = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.resolve_tool_details", mock_resolve
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.db", mock_db
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.ensure_user_doc", mock_ensure
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.users_collection", mock_users_col
|
||||
):
|
||||
with app.test_request_context("/api/shared_agent?token=abc123"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "owner1"}
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 200
|
||||
mock_ensure.assert_not_called()
|
||||
mock_users_col.update_one.assert_not_called()
|
||||
|
||||
def test_enriches_tool_names(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
tool_id = str(ObjectId())
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "owner1",
|
||||
"name": "Agent",
|
||||
"tools": [tool_id],
|
||||
"shared_publicly": True,
|
||||
"shared_token": "tok",
|
||||
}
|
||||
mock_tools_col = Mock()
|
||||
mock_tools_col.find_one.return_value = {
|
||||
"_id": ObjectId(tool_id),
|
||||
"name": "calculator",
|
||||
}
|
||||
mock_resolve = Mock(return_value=[])
|
||||
mock_db = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.user_tools_collection", mock_tools_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.resolve_tool_details", mock_resolve
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.db", mock_db
|
||||
):
|
||||
with app.test_request_context("/api/shared_agent?token=tok"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 200
|
||||
assert response.json["tools"] == ["calculator"]
|
||||
|
||||
def test_handles_source_dbref(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
source_id = ObjectId()
|
||||
source_ref = DBRef("sources", source_id)
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "owner1",
|
||||
"name": "Agent",
|
||||
"source": source_ref,
|
||||
"tools": [],
|
||||
"shared_publicly": True,
|
||||
"shared_token": "tok",
|
||||
}
|
||||
mock_resolve = Mock(return_value=[])
|
||||
mock_db = Mock()
|
||||
mock_db.dereference.return_value = {"_id": source_id}
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.resolve_tool_details", mock_resolve
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.db", mock_db
|
||||
):
|
||||
with app.test_request_context("/api/shared_agent?token=tok"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 200
|
||||
assert response.json["source"] == str(source_id)
|
||||
|
||||
def test_returns_400_on_exception(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
mock_col = Mock()
|
||||
mock_col.find_one.side_effect = Exception("DB error")
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_col
|
||||
):
|
||||
with app.test_request_context("/api/shared_agent?token=tok"):
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_tool_enrichment_handles_missing_tool(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
tool_id = str(ObjectId())
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "owner1",
|
||||
"name": "Agent",
|
||||
"tools": [tool_id],
|
||||
"shared_publicly": True,
|
||||
"shared_token": "tok",
|
||||
}
|
||||
mock_tools_col = Mock()
|
||||
mock_tools_col.find_one.return_value = None
|
||||
mock_resolve = Mock(return_value=[])
|
||||
mock_db = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.user_tools_collection", mock_tools_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.resolve_tool_details", mock_resolve
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.db", mock_db
|
||||
):
|
||||
with app.test_request_context("/api/shared_agent?token=tok"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 200
|
||||
# Missing tools are skipped
|
||||
assert response.json["tools"] == []
|
||||
|
||||
def test_image_url_generated_when_present(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "owner1",
|
||||
"name": "Agent",
|
||||
"image": "path/to/img.png",
|
||||
"tools": [],
|
||||
"shared_publicly": True,
|
||||
"shared_token": "tok",
|
||||
}
|
||||
mock_resolve = Mock(return_value=[])
|
||||
mock_db = Mock()
|
||||
mock_generate = Mock(return_value="http://example.com/img.png")
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.resolve_tool_details", mock_resolve
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.db", mock_db
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.generate_image_url", mock_generate
|
||||
):
|
||||
with app.test_request_context("/api/shared_agent?token=tok"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 200
|
||||
assert response.json["image"] == "http://example.com/img.png"
|
||||
mock_generate.assert_called_once_with("path/to/img.png")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SharedAgents (GET /shared_agents)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSharedAgents:
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgents
|
||||
|
||||
with app.test_request_context("/api/shared_agents"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = SharedAgents().get()
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_shared_agents_list(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgents
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_ensure = Mock(
|
||||
return_value={
|
||||
"user_id": "user1",
|
||||
"agent_preferences": {
|
||||
"shared_with_me": [str(agent_id)],
|
||||
"pinned": [str(agent_id)],
|
||||
},
|
||||
}
|
||||
)
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find.return_value = [
|
||||
{
|
||||
"_id": agent_id,
|
||||
"name": "Shared Agent",
|
||||
"description": "desc",
|
||||
"tools": [],
|
||||
"agent_type": "classic",
|
||||
"status": "published",
|
||||
"shared_publicly": True,
|
||||
"shared_token": "tok123",
|
||||
}
|
||||
]
|
||||
mock_resolve = Mock(return_value=[])
|
||||
mock_users_col = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.ensure_user_doc", mock_ensure
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.resolve_tool_details", mock_resolve
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.users_collection", mock_users_col
|
||||
):
|
||||
with app.test_request_context("/api/shared_agents"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = SharedAgents().get()
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
assert len(data) == 1
|
||||
assert data[0]["name"] == "Shared Agent"
|
||||
assert data[0]["pinned"] is True
|
||||
|
||||
def test_removes_stale_shared_ids(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgents
|
||||
|
||||
stale_id = str(ObjectId())
|
||||
mock_ensure = Mock(
|
||||
return_value={
|
||||
"user_id": "user1",
|
||||
"agent_preferences": {
|
||||
"shared_with_me": [stale_id],
|
||||
"pinned": [],
|
||||
},
|
||||
}
|
||||
)
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find.return_value = [] # None found
|
||||
mock_users_col = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.ensure_user_doc", mock_ensure
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.users_collection", mock_users_col
|
||||
):
|
||||
with app.test_request_context("/api/shared_agents"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = SharedAgents().get()
|
||||
assert response.status_code == 200
|
||||
mock_users_col.update_one.assert_called_once()
|
||||
call_args = mock_users_col.update_one.call_args
|
||||
assert stale_id in call_args[0][1]["$pullAll"][
|
||||
"agent_preferences.shared_with_me"
|
||||
]
|
||||
|
||||
def test_returns_empty_when_no_shared_ids(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgents
|
||||
|
||||
mock_ensure = Mock(
|
||||
return_value={
|
||||
"user_id": "user1",
|
||||
"agent_preferences": {"shared_with_me": [], "pinned": []},
|
||||
}
|
||||
)
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find.return_value = []
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.ensure_user_doc", mock_ensure
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
):
|
||||
with app.test_request_context("/api/shared_agents"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = SharedAgents().get()
|
||||
assert response.status_code == 200
|
||||
assert response.json == []
|
||||
|
||||
def test_returns_400_on_exception(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgents
|
||||
|
||||
mock_ensure = Mock(side_effect=Exception("DB error"))
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.ensure_user_doc", mock_ensure
|
||||
):
|
||||
with app.test_request_context("/api/shared_agents"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = SharedAgents().get()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_image_url_generated(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgents
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_ensure = Mock(
|
||||
return_value={
|
||||
"user_id": "user1",
|
||||
"agent_preferences": {
|
||||
"shared_with_me": [str(agent_id)],
|
||||
"pinned": [],
|
||||
},
|
||||
}
|
||||
)
|
||||
mock_agents_col = Mock()
|
||||
mock_agents_col.find.return_value = [
|
||||
{
|
||||
"_id": agent_id,
|
||||
"name": "Agent",
|
||||
"image": "path.png",
|
||||
"tools": [],
|
||||
"shared_publicly": True,
|
||||
}
|
||||
]
|
||||
mock_resolve = Mock(return_value=[])
|
||||
mock_generate = Mock(return_value="http://example.com/path.png")
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.ensure_user_doc", mock_ensure
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_agents_col
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.resolve_tool_details", mock_resolve
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.generate_image_url", mock_generate
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.users_collection", Mock()
|
||||
):
|
||||
with app.test_request_context("/api/shared_agents"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = SharedAgents().get()
|
||||
assert response.status_code == 200
|
||||
assert response.json[0]["image"] == "http://example.com/path.png"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ShareAgent (PUT /share_agent)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestShareAgent:
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={"id": "abc", "shared": True},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_400_missing_json_body(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
content_type="application/json",
|
||||
data=b"{}",
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
# Empty JSON object -> no id, no shared -> 400
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 400
|
||||
assert response.json["success"] is False
|
||||
|
||||
def test_returns_400_missing_id(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={"shared": True},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_400_missing_shared_param(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={"id": str(ObjectId())},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_400_invalid_agent_id(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={"id": "invalid-oid", "shared": True},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_agent_not_found(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
mock_col = Mock()
|
||||
mock_col.find_one.return_value = None
|
||||
agent_id = str(ObjectId())
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_col
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={"id": agent_id, "shared": True},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_shares_agent_success(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_col = Mock()
|
||||
mock_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "user1",
|
||||
}
|
||||
mock_col.update_one.return_value = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_col
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={
|
||||
"id": str(agent_id),
|
||||
"shared": True,
|
||||
"username": "TestUser",
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
assert data["success"] is True
|
||||
assert data["shared_token"] is not None
|
||||
mock_col.update_one.assert_called_once()
|
||||
|
||||
def test_unshares_agent_success(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_col = Mock()
|
||||
mock_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "user1",
|
||||
}
|
||||
mock_col.update_one.return_value = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_col
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={
|
||||
"id": str(agent_id),
|
||||
"shared": False,
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
assert data["success"] is True
|
||||
assert data["shared_token"] is None
|
||||
|
||||
def test_returns_400_on_db_exception(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_col = Mock()
|
||||
mock_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "user1",
|
||||
}
|
||||
mock_col.update_one.side_effect = Exception("DB error")
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_col
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={
|
||||
"id": str(agent_id),
|
||||
"shared": True,
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_share_with_username(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_col = Mock()
|
||||
mock_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "user1",
|
||||
}
|
||||
mock_col.update_one.return_value = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_col
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={
|
||||
"id": str(agent_id),
|
||||
"shared": True,
|
||||
"username": "SharedByUser",
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 200
|
||||
# Verify the update call includes shared_metadata with username
|
||||
update_call = mock_col.update_one.call_args[0][1]["$set"]
|
||||
assert update_call["shared_metadata"]["shared_by"] == "SharedByUser"
|
||||
assert update_call["shared_publicly"] is True
|
||||
assert "shared_token" in update_call
|
||||
|
||||
def test_shared_false_explicitly(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_col = Mock()
|
||||
mock_col.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "user1",
|
||||
}
|
||||
mock_col.update_one.return_value = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.agents_collection", mock_col
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={
|
||||
"id": str(agent_id),
|
||||
"shared": False,
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 200
|
||||
update_call = mock_col.update_one.call_args[0][1]
|
||||
assert update_call["$set"]["shared_publicly"] is False
|
||||
assert update_call["$set"]["shared_token"] is None
|
||||
@@ -0,0 +1,388 @@
|
||||
import datetime
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetMessageAnalytics:
|
||||
|
||||
def test_returns_message_analytics_last_30_days(self, app):
|
||||
from application.api.user.analytics.routes import GetMessageAnalytics
|
||||
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.aggregate.return_value = [
|
||||
{"_id": "2024-06-01", "count": 5},
|
||||
{"_id": "2024-06-02", "count": 3},
|
||||
]
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_message_analytics",
|
||||
method="POST",
|
||||
json={"filter_option": "last_30_days"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetMessageAnalytics().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
assert "messages" in response.json
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.analytics.routes import GetMessageAnalytics
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/get_message_analytics",
|
||||
method="POST",
|
||||
json={"filter_option": "last_30_days"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = GetMessageAnalytics().post()
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_400_invalid_filter_option(self, app):
|
||||
from application.api.user.analytics.routes import GetMessageAnalytics
|
||||
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_message_analytics",
|
||||
method="POST",
|
||||
json={"filter_option": "invalid_option"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetMessageAnalytics().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_filters_by_api_key(self, app):
|
||||
from application.api.user.analytics.routes import GetMessageAnalytics
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"key": "api_key_value",
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.aggregate.return_value = []
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
), patch(
|
||||
"application.api.user.analytics.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_message_analytics",
|
||||
method="POST",
|
||||
json={
|
||||
"filter_option": "last_7_days",
|
||||
"api_key_id": str(agent_id),
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetMessageAnalytics().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
pipeline = mock_conversations.aggregate.call_args[0][0]
|
||||
assert pipeline[0]["$match"].get("api_key") == "api_key_value"
|
||||
|
||||
def test_last_hour_filter(self, app):
|
||||
from application.api.user.analytics.routes import GetMessageAnalytics
|
||||
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.aggregate.return_value = []
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_message_analytics",
|
||||
method="POST",
|
||||
json={"filter_option": "last_hour"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetMessageAnalytics().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_last_24_hour_filter(self, app):
|
||||
from application.api.user.analytics.routes import GetMessageAnalytics
|
||||
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.aggregate.return_value = []
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_message_analytics",
|
||||
method="POST",
|
||||
json={"filter_option": "last_24_hour"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetMessageAnalytics().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetTokenAnalytics:
|
||||
|
||||
def test_returns_token_analytics(self, app):
|
||||
from application.api.user.analytics.routes import GetTokenAnalytics
|
||||
|
||||
mock_token_usage = Mock()
|
||||
mock_token_usage.aggregate.return_value = [
|
||||
{"_id": {"day": "2024-06-01"}, "total_tokens": 1000}
|
||||
]
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.token_usage_collection",
|
||||
mock_token_usage,
|
||||
), patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_token_analytics",
|
||||
method="POST",
|
||||
json={"filter_option": "last_30_days"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetTokenAnalytics().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
assert "token_usage" in response.json
|
||||
|
||||
def test_returns_400_invalid_filter(self, app):
|
||||
from application.api.user.analytics.routes import GetTokenAnalytics
|
||||
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_token_analytics",
|
||||
method="POST",
|
||||
json={"filter_option": "invalid"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetTokenAnalytics().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetFeedbackAnalytics:
|
||||
|
||||
def test_returns_feedback_analytics(self, app):
|
||||
from application.api.user.analytics.routes import GetFeedbackAnalytics
|
||||
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.aggregate.return_value = [
|
||||
{"_id": "2024-06-01", "positive": 10, "negative": 2}
|
||||
]
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_feedback_analytics",
|
||||
method="POST",
|
||||
json={"filter_option": "last_30_days"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetFeedbackAnalytics().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
assert "feedback" in response.json
|
||||
|
||||
def test_returns_400_invalid_filter(self, app):
|
||||
from application.api.user.analytics.routes import GetFeedbackAnalytics
|
||||
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_feedback_analytics",
|
||||
method="POST",
|
||||
json={"filter_option": "bad"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetFeedbackAnalytics().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetUserLogs:
|
||||
|
||||
def test_returns_paginated_logs(self, app):
|
||||
from application.api.user.analytics.routes import GetUserLogs
|
||||
|
||||
log_id = ObjectId()
|
||||
mock_cursor = Mock()
|
||||
mock_cursor.sort.return_value.skip.return_value.limit.return_value = [
|
||||
{
|
||||
"_id": log_id,
|
||||
"action": "query",
|
||||
"level": "info",
|
||||
"user": "user1",
|
||||
"question": "test?",
|
||||
"sources": [],
|
||||
"retriever_params": {},
|
||||
"timestamp": datetime.datetime(2024, 6, 1),
|
||||
}
|
||||
]
|
||||
mock_user_logs = Mock()
|
||||
mock_user_logs.find.return_value = mock_cursor
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.user_logs_collection",
|
||||
mock_user_logs,
|
||||
), patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_user_logs",
|
||||
method="POST",
|
||||
json={"page": 1, "page_size": 10},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetUserLogs().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
assert response.json["page"] == 1
|
||||
assert len(response.json["logs"]) == 1
|
||||
assert response.json["has_more"] is False
|
||||
|
||||
def test_detects_has_more(self, app):
|
||||
from application.api.user.analytics.routes import GetUserLogs
|
||||
|
||||
items = [
|
||||
{"_id": ObjectId(), "action": f"q{i}", "level": "info"}
|
||||
for i in range(3)
|
||||
]
|
||||
mock_cursor = Mock()
|
||||
mock_cursor.sort.return_value.skip.return_value.limit.return_value = items
|
||||
mock_user_logs = Mock()
|
||||
mock_user_logs.find.return_value = mock_cursor
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.analytics.routes.user_logs_collection",
|
||||
mock_user_logs,
|
||||
), patch(
|
||||
"application.api.user.analytics.routes.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/get_user_logs",
|
||||
method="POST",
|
||||
json={"page": 1, "page_size": 2},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetUserLogs().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["has_more"] is True
|
||||
assert len(response.json["logs"]) == 2
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.analytics.routes import GetUserLogs
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/get_user_logs",
|
||||
method="POST",
|
||||
json={"page": 1},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = GetUserLogs().post()
|
||||
|
||||
assert response.status_code == 401
|
||||
@@ -0,0 +1,360 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDeleteConversation:
|
||||
|
||||
def test_deletes_conversation(self, app):
|
||||
from application.api.user.conversations.routes import DeleteConversation
|
||||
|
||||
conv_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.conversations.routes.conversations_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(f"/api/delete_conversation?id={conv_id}"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = DeleteConversation().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
mock_collection.delete_one.assert_called_once_with(
|
||||
{"_id": conv_id, "user": "user1"}
|
||||
)
|
||||
|
||||
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:
|
||||
|
||||
def test_deletes_all_for_user(self, app):
|
||||
from application.api.user.conversations.routes import DeleteAllConversations
|
||||
|
||||
mock_collection = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.conversations.routes.conversations_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/delete_all_conversations"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = DeleteAllConversations().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_collection.delete_many.assert_called_once_with({"user": "user1"})
|
||||
|
||||
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:
|
||||
|
||||
def test_returns_conversations(self, app):
|
||||
from application.api.user.conversations.routes import GetConversations
|
||||
|
||||
conv_id = ObjectId()
|
||||
mock_cursor = Mock()
|
||||
mock_cursor.sort.return_value.limit.return_value = [
|
||||
{
|
||||
"_id": conv_id,
|
||||
"name": "Test Chat",
|
||||
"agent_id": "agent1",
|
||||
"is_shared_usage": False,
|
||||
}
|
||||
]
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = mock_cursor
|
||||
|
||||
with patch(
|
||||
"application.api.user.conversations.routes.conversations_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/get_conversations"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetConversations().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
assert len(data) == 1
|
||||
assert data[0]["id"] == str(conv_id)
|
||||
assert data[0]["name"] == "Test Chat"
|
||||
|
||||
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:
|
||||
|
||||
def test_returns_conversation(self, app):
|
||||
from application.api.user.conversations.routes import GetSingleConversation
|
||||
|
||||
conv_id = ObjectId()
|
||||
mock_conv_collection = Mock()
|
||||
mock_conv_collection.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Chat",
|
||||
"queries": [{"prompt": "hi", "response": "hello"}],
|
||||
"agent_id": "agent1",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.conversations.routes.conversations_collection",
|
||||
mock_conv_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/get_single_conversation?id={conv_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetSingleConversation().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["queries"] == [{"prompt": "hi", "response": "hello"}]
|
||||
assert response.json["agent_id"] == "agent1"
|
||||
|
||||
def test_returns_404_not_found(self, app):
|
||||
from application.api.user.conversations.routes import GetSingleConversation
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.conversations.routes.conversations_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/get_single_conversation?id={ObjectId()}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetSingleConversation().get()
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
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
|
||||
|
||||
def test_resolves_attachments(self, app):
|
||||
from application.api.user.conversations.routes import GetSingleConversation
|
||||
|
||||
conv_id = ObjectId()
|
||||
att_id = ObjectId()
|
||||
mock_conv_collection = Mock()
|
||||
mock_conv_collection.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Chat",
|
||||
"queries": [
|
||||
{"prompt": "hi", "response": "hello", "attachments": [str(att_id)]}
|
||||
],
|
||||
}
|
||||
mock_att_collection = Mock()
|
||||
mock_att_collection.find_one.return_value = {
|
||||
"_id": att_id,
|
||||
"filename": "doc.pdf",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.conversations.routes.conversations_collection",
|
||||
mock_conv_collection,
|
||||
), patch(
|
||||
"application.api.user.conversations.routes.attachments_collection",
|
||||
mock_att_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/get_single_conversation?id={conv_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetSingleConversation().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
attachments = response.json["queries"][0]["attachments"]
|
||||
assert len(attachments) == 1
|
||||
assert attachments[0]["fileName"] == "doc.pdf"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestUpdateConversationName:
|
||||
|
||||
def test_updates_name(self, app):
|
||||
from application.api.user.conversations.routes import UpdateConversationName
|
||||
|
||||
conv_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.conversations.routes.conversations_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/update_conversation_name",
|
||||
method="POST",
|
||||
json={"id": str(conv_id), "name": "New Name"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = UpdateConversationName().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
mock_collection.update_one.assert_called_once()
|
||||
|
||||
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(ObjectId())},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = UpdateConversationName().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSubmitFeedback:
|
||||
|
||||
def test_submits_positive_feedback(self, app):
|
||||
from application.api.user.conversations.routes import SubmitFeedback
|
||||
|
||||
conv_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.conversations.routes.conversations_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/feedback",
|
||||
method="POST",
|
||||
json={
|
||||
"feedback": "LIKE",
|
||||
"conversation_id": str(conv_id),
|
||||
"question_index": 0,
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = SubmitFeedback().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
call_args = mock_collection.update_one.call_args
|
||||
assert "$set" in call_args[0][1]
|
||||
|
||||
def test_removes_feedback_when_null(self, app):
|
||||
from application.api.user.conversations.routes import SubmitFeedback
|
||||
|
||||
conv_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.conversations.routes.conversations_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/feedback",
|
||||
method="POST",
|
||||
json={
|
||||
"feedback": None,
|
||||
"conversation_id": str(conv_id),
|
||||
"question_index": 0,
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = SubmitFeedback().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
call_args = mock_collection.update_one.call_args
|
||||
assert "$unset" in call_args[0][1]
|
||||
|
||||
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
|
||||
@@ -0,0 +1,509 @@
|
||||
import datetime
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentFoldersGet:
|
||||
|
||||
def test_returns_folders(self, app):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
now = datetime.datetime(2024, 6, 15, tzinfo=datetime.timezone.utc)
|
||||
folder_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = [
|
||||
{
|
||||
"_id": folder_id,
|
||||
"name": "My Folder",
|
||||
"parent_id": None,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
}
|
||||
]
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/agents/folders/", method="GET"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolders().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
folders = response.json["folders"]
|
||||
assert len(folders) == 1
|
||||
assert folders[0]["id"] == str(folder_id)
|
||||
assert folders[0]["name"] == "My Folder"
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
with app.test_request_context("/api/agents/folders/", method="GET"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = AgentFolders().get()
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentFoldersCreate:
|
||||
|
||||
def test_creates_folder(self, app):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
inserted_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.insert_one.return_value = Mock(inserted_id=inserted_id)
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/",
|
||||
method="POST",
|
||||
json={"name": "New Folder"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolders().post()
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.json["id"] == str(inserted_id)
|
||||
assert response.json["name"] == "New Folder"
|
||||
|
||||
def test_returns_400_missing_name(self, app):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/",
|
||||
method="POST",
|
||||
json={},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolders().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_validates_parent_folder_exists(self, app):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/",
|
||||
method="POST",
|
||||
json={"name": "Sub", "parent_id": str(ObjectId())},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolders().post()
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentFolderGet:
|
||||
|
||||
def test_returns_folder_with_agents_and_subfolders(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
folder_id = ObjectId()
|
||||
agent_id = ObjectId()
|
||||
subfolder_id = ObjectId()
|
||||
mock_folders = Mock()
|
||||
mock_folders.find_one.return_value = {
|
||||
"_id": folder_id,
|
||||
"name": "Folder",
|
||||
"parent_id": None,
|
||||
}
|
||||
mock_folders.find.return_value = [
|
||||
{"_id": subfolder_id, "name": "Subfolder"}
|
||||
]
|
||||
mock_agents = Mock()
|
||||
mock_agents.find.return_value = [
|
||||
{"_id": agent_id, "name": "Agent 1", "description": "Desc"}
|
||||
]
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_folders,
|
||||
), patch(
|
||||
"application.api.user.agents.folders.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/agents/folders/{folder_id}", method="GET"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolder().get(str(folder_id))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["name"] == "Folder"
|
||||
assert len(response.json["agents"]) == 1
|
||||
assert len(response.json["subfolders"]) == 1
|
||||
|
||||
def test_returns_404_not_found(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/agents/folders/{ObjectId()}", method="GET"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolder().get(str(ObjectId()))
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentFolderUpdate:
|
||||
|
||||
def test_updates_folder_name(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
folder_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.update_one.return_value = Mock(matched_count=1)
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/agents/folders/{folder_id}",
|
||||
method="PUT",
|
||||
json={"name": "Renamed"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolder().put(str(folder_id))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
|
||||
def test_prevents_self_parent(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
folder_id = str(ObjectId())
|
||||
|
||||
with app.test_request_context(
|
||||
f"/api/agents/folders/{folder_id}",
|
||||
method="PUT",
|
||||
json={"parent_id": folder_id},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolder().put(folder_id)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "own parent" in response.json["message"]
|
||||
|
||||
def test_returns_404_when_not_found(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.update_one.return_value = Mock(matched_count=0)
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/agents/folders/{ObjectId()}",
|
||||
method="PUT",
|
||||
json={"name": "X"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolder().put(str(ObjectId()))
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentFolderDelete:
|
||||
|
||||
def test_deletes_folder_and_unsets_references(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
folder_id = str(ObjectId())
|
||||
mock_folders = Mock()
|
||||
mock_folders.delete_one.return_value = Mock(deleted_count=1)
|
||||
mock_agents = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_folders,
|
||||
), patch(
|
||||
"application.api.user.agents.folders.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/agents/folders/{folder_id}", method="DELETE"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolder().delete(folder_id)
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_agents.update_many.assert_called_once()
|
||||
mock_folders.update_many.assert_called_once()
|
||||
mock_folders.delete_one.assert_called_once()
|
||||
|
||||
def test_returns_404_not_found(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
mock_folders = Mock()
|
||||
mock_folders.delete_one.return_value = Mock(deleted_count=0)
|
||||
mock_agents = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_folders,
|
||||
), patch(
|
||||
"application.api.user.agents.folders.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/agents/folders/{ObjectId()}", method="DELETE"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentFolder().delete(str(ObjectId()))
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestMoveAgentToFolder:
|
||||
|
||||
def test_moves_agent_to_folder(self, app):
|
||||
from application.api.user.agents.folders import MoveAgentToFolder
|
||||
|
||||
agent_id = ObjectId()
|
||||
folder_id = ObjectId()
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = {"_id": agent_id, "user": "user1"}
|
||||
mock_folders = Mock()
|
||||
mock_folders.find_one.return_value = {"_id": folder_id}
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agents_collection",
|
||||
mock_agents,
|
||||
), patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_folders,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/move_agent",
|
||||
method="POST",
|
||||
json={
|
||||
"agent_id": str(agent_id),
|
||||
"folder_id": str(folder_id),
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = MoveAgentToFolder().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_agents.update_one.assert_called_once()
|
||||
|
||||
def test_removes_agent_from_folder(self, app):
|
||||
from application.api.user.agents.folders import MoveAgentToFolder
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = {"_id": agent_id, "user": "user1"}
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/move_agent",
|
||||
method="POST",
|
||||
json={"agent_id": str(agent_id), "folder_id": None},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = MoveAgentToFolder().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
call_args = mock_agents.update_one.call_args
|
||||
assert "$unset" in call_args[0][1]
|
||||
|
||||
def test_returns_404_agent_not_found(self, app):
|
||||
from application.api.user.agents.folders import MoveAgentToFolder
|
||||
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/move_agent",
|
||||
method="POST",
|
||||
json={"agent_id": str(ObjectId())},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = MoveAgentToFolder().post()
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_returns_400_missing_agent_id(self, app):
|
||||
from application.api.user.agents.folders import MoveAgentToFolder
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/move_agent",
|
||||
method="POST",
|
||||
json={},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = MoveAgentToFolder().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestBulkMoveAgents:
|
||||
|
||||
def test_bulk_moves_to_folder(self, app):
|
||||
from application.api.user.agents.folders import BulkMoveAgents
|
||||
|
||||
folder_id = ObjectId()
|
||||
agent_ids = [str(ObjectId()), str(ObjectId())]
|
||||
mock_agents = Mock()
|
||||
mock_folders = Mock()
|
||||
mock_folders.find_one.return_value = {"_id": folder_id}
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agents_collection",
|
||||
mock_agents,
|
||||
), patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_folders,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/bulk_move",
|
||||
method="POST",
|
||||
json={"agent_ids": agent_ids, "folder_id": str(folder_id)},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = BulkMoveAgents().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_agents.update_many.assert_called_once()
|
||||
|
||||
def test_bulk_removes_from_folders(self, app):
|
||||
from application.api.user.agents.folders import BulkMoveAgents
|
||||
|
||||
agent_ids = [str(ObjectId())]
|
||||
mock_agents = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agents_collection",
|
||||
mock_agents,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/bulk_move",
|
||||
method="POST",
|
||||
json={"agent_ids": agent_ids},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = BulkMoveAgents().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
call_args = mock_agents.update_many.call_args
|
||||
assert "$unset" in call_args[0][1]
|
||||
|
||||
def test_returns_400_missing_agent_ids(self, app):
|
||||
from application.api.user.agents.folders import BulkMoveAgents
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/bulk_move",
|
||||
method="POST",
|
||||
json={},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = BulkMoveAgents().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_folder_not_found(self, app):
|
||||
from application.api.user.agents.folders import BulkMoveAgents
|
||||
|
||||
mock_folders = Mock()
|
||||
mock_folders.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.agent_folders_collection",
|
||||
mock_folders,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/bulk_move",
|
||||
method="POST",
|
||||
json={
|
||||
"agent_ids": [str(ObjectId())],
|
||||
"folder_id": str(ObjectId()),
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = BulkMoveAgents().post()
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -0,0 +1,70 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestModelsListResource:
|
||||
|
||||
def test_returns_models(self, app):
|
||||
from application.api.user.models.routes import ModelsListResource
|
||||
|
||||
mock_model = Mock()
|
||||
mock_model.to_dict.return_value = {
|
||||
"id": "gpt-4",
|
||||
"name": "GPT-4",
|
||||
"provider": "openai",
|
||||
}
|
||||
|
||||
mock_registry = Mock()
|
||||
mock_registry.get_enabled_models.return_value = [mock_model]
|
||||
mock_registry.default_model_id = "gpt-4"
|
||||
|
||||
with patch(
|
||||
"application.api.user.models.routes.ModelRegistry.get_instance",
|
||||
return_value=mock_registry,
|
||||
):
|
||||
with app.test_request_context("/api/models"):
|
||||
response = ModelsListResource().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["count"] == 1
|
||||
assert response.json["default_model_id"] == "gpt-4"
|
||||
assert response.json["models"][0]["id"] == "gpt-4"
|
||||
|
||||
def test_returns_empty_models(self, app):
|
||||
from application.api.user.models.routes import ModelsListResource
|
||||
|
||||
mock_registry = Mock()
|
||||
mock_registry.get_enabled_models.return_value = []
|
||||
mock_registry.default_model_id = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.models.routes.ModelRegistry.get_instance",
|
||||
return_value=mock_registry,
|
||||
):
|
||||
with app.test_request_context("/api/models"):
|
||||
response = ModelsListResource().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["count"] == 0
|
||||
assert response.json["models"] == []
|
||||
|
||||
def test_returns_500_on_error(self, app):
|
||||
from application.api.user.models.routes import ModelsListResource
|
||||
|
||||
with patch(
|
||||
"application.api.user.models.routes.ModelRegistry.get_instance",
|
||||
side_effect=Exception("Registry error"),
|
||||
):
|
||||
with app.test_request_context("/api/models"):
|
||||
response = ModelsListResource().get()
|
||||
|
||||
assert response.status_code == 500
|
||||
@@ -0,0 +1,288 @@
|
||||
from unittest.mock import Mock, mock_open, patch
|
||||
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestCreatePrompt:
|
||||
|
||||
def test_creates_prompt(self, app):
|
||||
from application.api.user.prompts.routes import CreatePrompt
|
||||
|
||||
mock_collection = Mock()
|
||||
inserted_id = ObjectId()
|
||||
mock_collection.insert_one.return_value = Mock(inserted_id=inserted_id)
|
||||
|
||||
with patch(
|
||||
"application.api.user.prompts.routes.prompts_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/create_prompt",
|
||||
method="POST",
|
||||
json={"name": "My Prompt", "content": "You are helpful."},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = CreatePrompt().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["id"] == str(inserted_id)
|
||||
mock_collection.insert_one.assert_called_once()
|
||||
doc = mock_collection.insert_one.call_args[0][0]
|
||||
assert doc["name"] == "My Prompt"
|
||||
assert doc["user"] == "user1"
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.prompts.routes import CreatePrompt
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/create_prompt",
|
||||
method="POST",
|
||||
json={"name": "P", "content": "C"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = CreatePrompt().post()
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_400_missing_fields(self, app):
|
||||
from application.api.user.prompts.routes import CreatePrompt
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/create_prompt",
|
||||
method="POST",
|
||||
json={"name": "P"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = CreatePrompt().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetPrompts:
|
||||
|
||||
def test_returns_prompts_with_defaults(self, app):
|
||||
from application.api.user.prompts.routes import GetPrompts
|
||||
|
||||
user_prompt_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find.return_value = [
|
||||
{"_id": user_prompt_id, "name": "Custom Prompt"}
|
||||
]
|
||||
|
||||
with patch(
|
||||
"application.api.user.prompts.routes.prompts_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context("/api/get_prompts"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetPrompts().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
public_names = [p["name"] for p in data if p["type"] == "public"]
|
||||
assert "default" in public_names
|
||||
assert "creative" in public_names
|
||||
assert "strict" in public_names
|
||||
private = [p for p in data if p["type"] == "private"]
|
||||
assert len(private) == 1
|
||||
assert private[0]["name"] == "Custom Prompt"
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.prompts.routes import GetPrompts
|
||||
|
||||
with app.test_request_context("/api/get_prompts"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = GetPrompts().get()
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetSinglePrompt:
|
||||
|
||||
def test_returns_default_prompt(self, app):
|
||||
from application.api.user.prompts.routes import GetSinglePrompt
|
||||
|
||||
with patch("builtins.open", mock_open(read_data="Default prompt content")):
|
||||
with app.test_request_context("/api/get_single_prompt?id=default"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetSinglePrompt().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["content"] == "Default prompt content"
|
||||
|
||||
def test_returns_creative_prompt(self, app):
|
||||
from application.api.user.prompts.routes import GetSinglePrompt
|
||||
|
||||
with patch("builtins.open", mock_open(read_data="Creative content")):
|
||||
with app.test_request_context("/api/get_single_prompt?id=creative"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetSinglePrompt().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["content"] == "Creative content"
|
||||
|
||||
def test_returns_strict_prompt(self, app):
|
||||
from application.api.user.prompts.routes import GetSinglePrompt
|
||||
|
||||
with patch("builtins.open", mock_open(read_data="Strict content")):
|
||||
with app.test_request_context("/api/get_single_prompt?id=strict"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetSinglePrompt().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["content"] == "Strict content"
|
||||
|
||||
def test_returns_custom_prompt(self, app):
|
||||
from application.api.user.prompts.routes import GetSinglePrompt
|
||||
|
||||
prompt_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": prompt_id,
|
||||
"content": "Custom content",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.prompts.routes.prompts_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/get_single_prompt?id={prompt_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetSinglePrompt().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["content"] == "Custom content"
|
||||
|
||||
def test_returns_400_missing_id(self, app):
|
||||
from application.api.user.prompts.routes import GetSinglePrompt
|
||||
|
||||
with app.test_request_context("/api/get_single_prompt"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = GetSinglePrompt().get()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDeletePrompt:
|
||||
|
||||
def test_deletes_prompt(self, app):
|
||||
from application.api.user.prompts.routes import DeletePrompt
|
||||
|
||||
prompt_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.prompts.routes.prompts_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/delete_prompt",
|
||||
method="POST",
|
||||
json={"id": str(prompt_id)},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = DeletePrompt().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
mock_collection.delete_one.assert_called_once_with(
|
||||
{"_id": prompt_id, "user": "user1"}
|
||||
)
|
||||
|
||||
def test_returns_400_missing_id(self, app):
|
||||
from application.api.user.prompts.routes import DeletePrompt
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/delete_prompt",
|
||||
method="POST",
|
||||
json={},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = DeletePrompt().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestUpdatePrompt:
|
||||
|
||||
def test_updates_prompt(self, app):
|
||||
from application.api.user.prompts.routes import UpdatePrompt
|
||||
|
||||
prompt_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.prompts.routes.prompts_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/update_prompt",
|
||||
method="POST",
|
||||
json={
|
||||
"id": str(prompt_id),
|
||||
"name": "Updated",
|
||||
"content": "New content",
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = UpdatePrompt().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
mock_collection.update_one.assert_called_once()
|
||||
|
||||
def test_returns_400_missing_fields(self, app):
|
||||
from application.api.user.prompts.routes import UpdatePrompt
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/update_prompt",
|
||||
method="POST",
|
||||
json={"id": str(ObjectId()), "name": "Updated"},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = UpdatePrompt().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
@@ -0,0 +1,690 @@
|
||||
import uuid
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
from bson.binary import Binary, UuidRepresentation
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestShareConversation:
|
||||
|
||||
def test_shares_non_promptable_conversation(self, app):
|
||||
from application.api.user.sharing.routes import ShareConversation
|
||||
|
||||
conv_id = ObjectId()
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Test Chat",
|
||||
"queries": [{"prompt": "hi"}],
|
||||
}
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = None
|
||||
mock_shared.insert_one.return_value = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share?isPromptable=false",
|
||||
method="POST",
|
||||
json={"conversation_id": str(conv_id)},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareConversation().post()
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.json["success"] is True
|
||||
assert "identifier" in response.json
|
||||
mock_shared.insert_one.assert_called_once()
|
||||
|
||||
def test_returns_existing_shared_link(self, app):
|
||||
from application.api.user.sharing.routes import ShareConversation
|
||||
|
||||
conv_id = ObjectId()
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Test Chat",
|
||||
"queries": [{"prompt": "hi"}],
|
||||
}
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": conv_id,
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share?isPromptable=false",
|
||||
method="POST",
|
||||
json={"conversation_id": str(conv_id)},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareConversation().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["identifier"] == str(test_uuid)
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.sharing.routes import ShareConversation
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/share?isPromptable=false",
|
||||
method="POST",
|
||||
json={"conversation_id": str(ObjectId())},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = ShareConversation().post()
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_400_missing_conversation_id(self, app):
|
||||
from application.api.user.sharing.routes import ShareConversation
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/share?isPromptable=false",
|
||||
method="POST",
|
||||
json={},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareConversation().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_400_missing_isPromptable(self, app):
|
||||
from application.api.user.sharing.routes import ShareConversation
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/share",
|
||||
method="POST",
|
||||
json={"conversation_id": str(ObjectId())},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareConversation().post()
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "isPromptable" in response.json["message"]
|
||||
|
||||
def test_returns_404_conversation_not_found(self, app):
|
||||
from application.api.user.sharing.routes import ShareConversation
|
||||
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share?isPromptable=false",
|
||||
method="POST",
|
||||
json={"conversation_id": str(ObjectId())},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareConversation().post()
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetPubliclySharedConversations:
|
||||
|
||||
def test_returns_shared_conversation(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": conv_id,
|
||||
"first_n_queries": 2,
|
||||
"isPromptable": False,
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Shared Chat",
|
||||
"queries": [
|
||||
{"prompt": "q1", "response": "a1"},
|
||||
{"prompt": "q2", "response": "a2"},
|
||||
{"prompt": "q3", "response": "a3"},
|
||||
],
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
assert response.json["title"] == "Shared Chat"
|
||||
assert len(response.json["queries"]) == 2
|
||||
|
||||
def test_returns_404_not_found(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_returns_404_conversation_deleted(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": conv_id,
|
||||
"first_n_queries": 1,
|
||||
"isPromptable": False,
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_includes_api_key_when_promptable(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": conv_id,
|
||||
"first_n_queries": 1,
|
||||
"isPromptable": True,
|
||||
"api_key": "shared_api_key",
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Chat",
|
||||
"queries": [{"prompt": "q1", "response": "a1"}],
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["api_key"] == "shared_api_key"
|
||||
|
||||
def test_handles_dbref_conversation_id(self, app):
|
||||
from bson.dbref import DBRef
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": DBRef("conversations", conv_id),
|
||||
"first_n_queries": 1,
|
||||
"isPromptable": False,
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Chat",
|
||||
"queries": [{"prompt": "q1", "response": "a1"}],
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_conversations.find_one.assert_called_once_with({"_id": conv_id})
|
||||
|
||||
def test_handles_dict_oid_conversation_id(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": {"$id": {"$oid": str(conv_id)}},
|
||||
"first_n_queries": 1,
|
||||
"isPromptable": False,
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Chat",
|
||||
"queries": [{"prompt": "q1", "response": "a1"}],
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_handles_dict_id_string_conversation_id(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": {"$id": str(conv_id)},
|
||||
"first_n_queries": 1,
|
||||
"isPromptable": False,
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Chat",
|
||||
"queries": [{"prompt": "q1", "response": "a1"}],
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_handles_dict_underscore_id_conversation_id(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": {"_id": str(conv_id)},
|
||||
"first_n_queries": 1,
|
||||
"isPromptable": False,
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Chat",
|
||||
"queries": [{"prompt": "q1", "response": "a1"}],
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_handles_string_conversation_id(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": str(conv_id),
|
||||
"first_n_queries": 1,
|
||||
"isPromptable": False,
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Chat",
|
||||
"queries": [{"prompt": "q1", "response": "a1"}],
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_resolves_attachments_in_shared(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
conv_id = ObjectId()
|
||||
att_id = ObjectId()
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {
|
||||
"uuid": binary_uuid,
|
||||
"conversation_id": conv_id,
|
||||
"first_n_queries": 1,
|
||||
"isPromptable": False,
|
||||
}
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Chat",
|
||||
"queries": [
|
||||
{"prompt": "q1", "response": "a1", "attachments": [str(att_id)]}
|
||||
],
|
||||
}
|
||||
mock_attachments = Mock()
|
||||
mock_attachments.find_one.return_value = {
|
||||
"_id": att_id,
|
||||
"filename": "file.pdf",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.attachments_collection",
|
||||
mock_attachments,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{test_uuid}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(test_uuid))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["queries"][0]["attachments"][0]["fileName"] == "file.pdf"
|
||||
|
||||
def test_handles_general_exception(self, app):
|
||||
from application.api.user.sharing.routes import (
|
||||
GetPubliclySharedConversations,
|
||||
)
|
||||
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.side_effect = Exception("DB error")
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/shared_conversation/{uuid.uuid4()}"
|
||||
):
|
||||
response = GetPubliclySharedConversations().get(str(uuid.uuid4()))
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestShareConversationPromptable:
|
||||
|
||||
def test_promptable_with_existing_api_key_and_existing_share(self, app):
|
||||
from application.api.user.sharing.routes import ShareConversation
|
||||
|
||||
conv_id = ObjectId()
|
||||
test_uuid = uuid.uuid4()
|
||||
binary_uuid = Binary.from_uuid(test_uuid, UuidRepresentation.STANDARD)
|
||||
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Test Chat",
|
||||
"queries": [{"prompt": "hi"}],
|
||||
}
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = {"key": "existing_api_uuid"}
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = {"uuid": binary_uuid}
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.agents_collection",
|
||||
mock_agents,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share?isPromptable=true",
|
||||
method="POST",
|
||||
json={
|
||||
"conversation_id": str(conv_id),
|
||||
"prompt_id": "default",
|
||||
"chunks": "3",
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareConversation().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["identifier"] == str(test_uuid)
|
||||
|
||||
def test_promptable_with_existing_api_key_new_share(self, app):
|
||||
from application.api.user.sharing.routes import ShareConversation
|
||||
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Test Chat",
|
||||
"queries": [{"prompt": "hi"}],
|
||||
}
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = {"key": "existing_api_uuid"}
|
||||
mock_shared = Mock()
|
||||
mock_shared.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.agents_collection",
|
||||
mock_agents,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share?isPromptable=true",
|
||||
method="POST",
|
||||
json={
|
||||
"conversation_id": str(conv_id),
|
||||
"source": str(ObjectId()),
|
||||
"retriever": "classic",
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareConversation().post()
|
||||
|
||||
assert response.status_code == 201
|
||||
mock_shared.insert_one.assert_called_once()
|
||||
|
||||
def test_promptable_creates_new_api_key(self, app):
|
||||
from application.api.user.sharing.routes import ShareConversation
|
||||
|
||||
conv_id = ObjectId()
|
||||
|
||||
mock_conversations = Mock()
|
||||
mock_conversations.find_one.return_value = {
|
||||
"_id": conv_id,
|
||||
"name": "Test Chat",
|
||||
"queries": [{"prompt": "hi"}],
|
||||
}
|
||||
mock_agents = Mock()
|
||||
mock_agents.find_one.return_value = None
|
||||
mock_shared = Mock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.sharing.routes.conversations_collection",
|
||||
mock_conversations,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.agents_collection",
|
||||
mock_agents,
|
||||
), patch(
|
||||
"application.api.user.sharing.routes.shared_conversations_collections",
|
||||
mock_shared,
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/api/share?isPromptable=true",
|
||||
method="POST",
|
||||
json={
|
||||
"conversation_id": str(conv_id),
|
||||
"source": str(ObjectId()),
|
||||
"retriever": "classic",
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = ShareConversation().post()
|
||||
|
||||
assert response.status_code == 201
|
||||
mock_agents.insert_one.assert_called_once()
|
||||
mock_shared.insert_one.assert_called_once()
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,411 @@
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetUserId:
|
||||
|
||||
def test_returns_user_id_from_decoded_token(self, app):
|
||||
from application.api.user.utils import get_user_id
|
||||
|
||||
with app.test_request_context():
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user_123"}
|
||||
assert get_user_id() == "user_123"
|
||||
|
||||
def test_returns_none_when_no_decoded_token(self, app):
|
||||
from application.api.user.utils import get_user_id
|
||||
|
||||
with app.test_request_context():
|
||||
assert get_user_id() is None
|
||||
|
||||
def test_returns_none_when_decoded_token_has_no_sub(self, app):
|
||||
from application.api.user.utils import get_user_id
|
||||
|
||||
with app.test_request_context():
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {}
|
||||
assert get_user_id() is None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestRequireAuth:
|
||||
|
||||
def test_allows_authenticated_request(self, app):
|
||||
from application.api.user.utils import require_auth
|
||||
|
||||
@require_auth
|
||||
def protected():
|
||||
return "ok"
|
||||
|
||||
with app.test_request_context():
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user_123"}
|
||||
assert protected() == "ok"
|
||||
|
||||
def test_returns_401_when_unauthenticated(self, app):
|
||||
from application.api.user.utils import require_auth
|
||||
|
||||
@require_auth
|
||||
def protected():
|
||||
return "ok"
|
||||
|
||||
with app.test_request_context():
|
||||
result = protected()
|
||||
assert result.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSuccessResponse:
|
||||
|
||||
def test_default_success_response(self, app):
|
||||
from application.api.user.utils import success_response
|
||||
|
||||
with app.app_context():
|
||||
resp = success_response()
|
||||
assert resp.status_code == 200
|
||||
assert resp.json["success"] is True
|
||||
|
||||
def test_success_response_with_data(self, app):
|
||||
from application.api.user.utils import success_response
|
||||
|
||||
with app.app_context():
|
||||
resp = success_response({"items": [1, 2], "total": 2})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json["success"] is True
|
||||
assert resp.json["items"] == [1, 2]
|
||||
assert resp.json["total"] == 2
|
||||
|
||||
def test_success_response_custom_status(self, app):
|
||||
from application.api.user.utils import success_response
|
||||
|
||||
with app.app_context():
|
||||
resp = success_response({"id": "new"}, 201)
|
||||
assert resp.status_code == 201
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestErrorResponse:
|
||||
|
||||
def test_default_error_response(self, app):
|
||||
from application.api.user.utils import error_response
|
||||
|
||||
with app.app_context():
|
||||
resp = error_response("Something went wrong")
|
||||
assert resp.status_code == 400
|
||||
assert resp.json["success"] is False
|
||||
assert resp.json["message"] == "Something went wrong"
|
||||
|
||||
def test_error_response_custom_status(self, app):
|
||||
from application.api.user.utils import error_response
|
||||
|
||||
with app.app_context():
|
||||
resp = error_response("Not found", 404)
|
||||
assert resp.status_code == 404
|
||||
|
||||
def test_error_response_extra_kwargs(self, app):
|
||||
from application.api.user.utils import error_response
|
||||
|
||||
with app.app_context():
|
||||
resp = error_response("Bad", 400, errors=["field1", "field2"])
|
||||
assert resp.json["errors"] == ["field1", "field2"]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestValidateObjectId:
|
||||
|
||||
def test_valid_object_id(self, app):
|
||||
from application.api.user.utils import validate_object_id
|
||||
|
||||
with app.app_context():
|
||||
oid = ObjectId()
|
||||
result, error = validate_object_id(str(oid))
|
||||
assert result == oid
|
||||
assert error is None
|
||||
|
||||
def test_invalid_object_id(self, app):
|
||||
from application.api.user.utils import validate_object_id
|
||||
|
||||
with app.app_context():
|
||||
result, error = validate_object_id("not-a-valid-id")
|
||||
assert result is None
|
||||
assert error.status_code == 400
|
||||
assert "Invalid" in error.json["message"]
|
||||
|
||||
def test_custom_resource_name(self, app):
|
||||
from application.api.user.utils import validate_object_id
|
||||
|
||||
with app.app_context():
|
||||
_, error = validate_object_id("bad", "Workflow")
|
||||
assert "Workflow" in error.json["message"]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestValidatePagination:
|
||||
|
||||
def test_default_pagination(self, app):
|
||||
from application.api.user.utils import validate_pagination
|
||||
|
||||
with app.test_request_context("/?limit=10&skip=0"):
|
||||
limit, skip, error = validate_pagination()
|
||||
assert limit == 10
|
||||
assert skip == 0
|
||||
assert error is None
|
||||
|
||||
def test_uses_defaults_when_no_params(self, app):
|
||||
from application.api.user.utils import validate_pagination
|
||||
|
||||
with app.test_request_context("/"):
|
||||
limit, skip, error = validate_pagination()
|
||||
assert limit == 20
|
||||
assert skip == 0
|
||||
assert error is None
|
||||
|
||||
def test_enforces_max_limit(self, app):
|
||||
from application.api.user.utils import validate_pagination
|
||||
|
||||
with app.test_request_context("/?limit=500"):
|
||||
limit, _, _ = validate_pagination(max_limit=100)
|
||||
assert limit == 100
|
||||
|
||||
def test_invalid_limit(self, app):
|
||||
from application.api.user.utils import validate_pagination
|
||||
|
||||
with app.test_request_context("/?limit=-1"):
|
||||
_, _, error = validate_pagination()
|
||||
assert error is not None
|
||||
assert error.status_code == 400
|
||||
|
||||
def test_invalid_skip(self, app):
|
||||
from application.api.user.utils import validate_pagination
|
||||
|
||||
with app.test_request_context("/?skip=-1"):
|
||||
_, _, error = validate_pagination()
|
||||
assert error is not None
|
||||
|
||||
def test_non_numeric_values(self, app):
|
||||
from application.api.user.utils import validate_pagination
|
||||
|
||||
with app.test_request_context("/?limit=abc"):
|
||||
_, _, error = validate_pagination()
|
||||
assert error is not None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestCheckResourceOwnership:
|
||||
|
||||
def test_returns_resource_when_owned(self, app):
|
||||
from application.api.user.utils import check_resource_ownership
|
||||
|
||||
with app.app_context():
|
||||
collection = Mock()
|
||||
oid = ObjectId()
|
||||
doc = {"_id": oid, "user": "user1", "name": "test"}
|
||||
collection.find_one.return_value = doc
|
||||
|
||||
resource, error = check_resource_ownership(collection, oid, "user1")
|
||||
assert resource == doc
|
||||
assert error is None
|
||||
|
||||
def test_returns_404_when_not_found(self, app):
|
||||
from application.api.user.utils import check_resource_ownership
|
||||
|
||||
with app.app_context():
|
||||
collection = Mock()
|
||||
collection.find_one.return_value = None
|
||||
|
||||
resource, error = check_resource_ownership(
|
||||
collection, ObjectId(), "user1", "Workflow"
|
||||
)
|
||||
assert resource is None
|
||||
assert error.status_code == 404
|
||||
assert "Workflow" in error.json["message"]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSerializeObjectId:
|
||||
|
||||
def test_converts_id_to_string(self):
|
||||
from application.api.user.utils import serialize_object_id
|
||||
|
||||
oid = ObjectId()
|
||||
obj = {"_id": oid, "name": "test"}
|
||||
result = serialize_object_id(obj)
|
||||
assert result["id"] == str(oid)
|
||||
assert "_id" not in result
|
||||
|
||||
def test_custom_field_names(self):
|
||||
from application.api.user.utils import serialize_object_id
|
||||
|
||||
oid = ObjectId()
|
||||
obj = {"custom_id": oid}
|
||||
result = serialize_object_id(obj, id_field="custom_id", new_field="uid")
|
||||
assert result["uid"] == str(oid)
|
||||
assert "custom_id" not in result
|
||||
|
||||
def test_no_id_field_present(self):
|
||||
from application.api.user.utils import serialize_object_id
|
||||
|
||||
obj = {"name": "test"}
|
||||
result = serialize_object_id(obj)
|
||||
assert "id" not in result
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSerializeList:
|
||||
|
||||
def test_applies_serializer_to_all_items(self):
|
||||
from application.api.user.utils import serialize_list
|
||||
|
||||
items = [{"_id": ObjectId()}, {"_id": ObjectId()}]
|
||||
|
||||
def serializer(item):
|
||||
return {"id": str(item["_id"])}
|
||||
|
||||
result = serialize_list(items, serializer)
|
||||
assert len(result) == 2
|
||||
assert all("id" in r for r in result)
|
||||
|
||||
def test_empty_list(self):
|
||||
from application.api.user.utils import serialize_list
|
||||
|
||||
assert serialize_list([], lambda x: x) == []
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestRequireFields:
|
||||
|
||||
def test_allows_valid_request(self, app):
|
||||
from application.api.user.utils import require_fields
|
||||
|
||||
@require_fields(["name", "email"])
|
||||
def handler():
|
||||
return "ok"
|
||||
|
||||
with app.test_request_context(
|
||||
"/", method="POST", json={"name": "Alice", "email": "a@b.com"}
|
||||
):
|
||||
assert handler() == "ok"
|
||||
|
||||
def test_rejects_missing_fields(self, app):
|
||||
from application.api.user.utils import require_fields
|
||||
|
||||
@require_fields(["name", "email"])
|
||||
def handler():
|
||||
return "ok"
|
||||
|
||||
with app.test_request_context("/", method="POST", json={"name": "Alice"}):
|
||||
result = handler()
|
||||
assert result.status_code == 400
|
||||
assert "email" in result.json["message"]
|
||||
|
||||
def test_rejects_empty_body(self, app):
|
||||
from application.api.user.utils import require_fields
|
||||
|
||||
@require_fields(["name"])
|
||||
def handler():
|
||||
return "ok"
|
||||
|
||||
with app.test_request_context(
|
||||
"/", method="POST", json={}
|
||||
):
|
||||
result = handler()
|
||||
assert result.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSafeDbOperation:
|
||||
|
||||
def test_returns_result_on_success(self, app):
|
||||
from application.api.user.utils import safe_db_operation
|
||||
|
||||
with app.app_context():
|
||||
result, error = safe_db_operation(lambda: {"inserted": True})
|
||||
assert result == {"inserted": True}
|
||||
assert error is None
|
||||
|
||||
def test_returns_error_on_exception(self, app):
|
||||
from application.api.user.utils import safe_db_operation
|
||||
|
||||
with app.app_context():
|
||||
result, error = safe_db_operation(
|
||||
lambda: (_ for _ in ()).throw(RuntimeError("db error")),
|
||||
"Operation failed",
|
||||
)
|
||||
assert result is None
|
||||
assert error.status_code == 400
|
||||
assert error.json["message"] == "Operation failed"
|
||||
|
||||
def test_hides_exception_details(self, app):
|
||||
from application.api.user.utils import safe_db_operation
|
||||
|
||||
with app.app_context():
|
||||
_, error = safe_db_operation(
|
||||
lambda: (_ for _ in ()).throw(RuntimeError("secret credentials")),
|
||||
"Failed",
|
||||
)
|
||||
assert "credentials" not in error.json["message"]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestValidateEnum:
|
||||
|
||||
def test_valid_value(self, app):
|
||||
from application.api.user.utils import validate_enum
|
||||
|
||||
with app.app_context():
|
||||
assert validate_enum("draft", ["draft", "published"], "status") is None
|
||||
|
||||
def test_invalid_value(self, app):
|
||||
from application.api.user.utils import validate_enum
|
||||
|
||||
with app.app_context():
|
||||
error = validate_enum("unknown", ["draft", "published"], "status")
|
||||
assert error.status_code == 400
|
||||
assert "status" in error.json["message"]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestExtractSortParams:
|
||||
|
||||
def test_defaults(self, app):
|
||||
from application.api.user.utils import extract_sort_params
|
||||
|
||||
with app.test_request_context("/"):
|
||||
field, order = extract_sort_params()
|
||||
assert field == "created_at"
|
||||
assert order == -1
|
||||
|
||||
def test_custom_params(self, app):
|
||||
from application.api.user.utils import extract_sort_params
|
||||
|
||||
with app.test_request_context("/?sort=name&order=asc"):
|
||||
field, order = extract_sort_params()
|
||||
assert field == "name"
|
||||
assert order == 1
|
||||
|
||||
def test_enforces_allowed_fields(self, app):
|
||||
from application.api.user.utils import extract_sort_params
|
||||
|
||||
with app.test_request_context("/?sort=forbidden_field"):
|
||||
field, _ = extract_sort_params(allowed_fields=["name", "date"])
|
||||
assert field == "created_at"
|
||||
|
||||
def test_desc_order(self, app):
|
||||
from application.api.user.utils import extract_sort_params
|
||||
|
||||
with app.test_request_context("/?order=desc"):
|
||||
_, order = extract_sort_params()
|
||||
assert order == -1
|
||||
@@ -0,0 +1,225 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask(__name__)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentWebhook:
|
||||
|
||||
def test_returns_existing_webhook_url(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhook
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "user1",
|
||||
"incoming_webhook_token": "existing_token",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.webhooks.agents_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.agents.webhooks.settings",
|
||||
Mock(API_URL="https://api.example.com"),
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/agent_webhook?id={agent_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentWebhook().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
assert "existing_token" in response.json["webhook_url"]
|
||||
mock_collection.update_one.assert_not_called()
|
||||
|
||||
def test_generates_new_webhook_token(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhook
|
||||
|
||||
agent_id = ObjectId()
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = {
|
||||
"_id": agent_id,
|
||||
"user": "user1",
|
||||
"incoming_webhook_token": None,
|
||||
}
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.webhooks.agents_collection",
|
||||
mock_collection,
|
||||
), patch(
|
||||
"application.api.user.agents.webhooks.settings",
|
||||
Mock(API_URL="https://api.example.com"),
|
||||
), patch(
|
||||
"application.api.user.agents.webhooks.secrets.token_urlsafe",
|
||||
return_value="new_generated_token",
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/agent_webhook?id={agent_id}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentWebhook().get()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "new_generated_token" in response.json["webhook_url"]
|
||||
mock_collection.update_one.assert_called_once()
|
||||
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhook
|
||||
|
||||
with app.test_request_context(f"/api/agent_webhook?id={ObjectId()}"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = None
|
||||
response = AgentWebhook().get()
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_400_missing_id(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhook
|
||||
|
||||
with app.test_request_context("/api/agent_webhook"):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentWebhook().get()
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_agent_not_found(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhook
|
||||
|
||||
mock_collection = Mock()
|
||||
mock_collection.find_one.return_value = None
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.webhooks.agents_collection",
|
||||
mock_collection,
|
||||
):
|
||||
with app.test_request_context(
|
||||
f"/api/agent_webhook?id={ObjectId()}"
|
||||
):
|
||||
from flask import request
|
||||
|
||||
request.decoded_token = {"sub": "user1"}
|
||||
response = AgentWebhook().get()
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentWebhookListenerPost:
|
||||
|
||||
def test_enqueues_task_on_valid_post(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhookListener
|
||||
|
||||
mock_task = Mock()
|
||||
mock_task.id = "task_abc"
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.webhooks.process_agent_webhook"
|
||||
) as mock_process:
|
||||
mock_process.delay.return_value = mock_task
|
||||
with app.test_request_context(
|
||||
"/api/webhooks/agents/tok",
|
||||
method="POST",
|
||||
json={"event": "new_message"},
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
response = listener._enqueue_webhook_task(
|
||||
"agent123", {"event": "new_message"}, "POST"
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["task_id"] == "task_abc"
|
||||
mock_process.delay.assert_called_once_with(
|
||||
agent_id="agent123", payload={"event": "new_message"}
|
||||
)
|
||||
|
||||
def test_returns_400_on_missing_json(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhookListener
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/webhooks/agents/tok",
|
||||
method="POST",
|
||||
json=None,
|
||||
content_type="application/json",
|
||||
data="",
|
||||
):
|
||||
from flask import request as flask_request
|
||||
|
||||
# Force get_json to return None (simulating empty/missing body)
|
||||
with patch.object(
|
||||
flask_request, "get_json", return_value=None
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
response = listener.post(
|
||||
webhook_token="tok",
|
||||
agent={"_id": ObjectId()},
|
||||
agent_id_str="agent123",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_handles_enqueue_error(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhookListener
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.webhooks.process_agent_webhook"
|
||||
) as mock_process:
|
||||
mock_process.delay.side_effect = Exception("Queue down")
|
||||
with app.test_request_context(
|
||||
"/api/webhooks/agents/tok",
|
||||
method="POST",
|
||||
json={"event": "test"},
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
response = listener._enqueue_webhook_task(
|
||||
"agent123", {"event": "test"}, "POST"
|
||||
)
|
||||
|
||||
assert response.status_code == 500
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentWebhookListenerGet:
|
||||
|
||||
def test_uses_query_params_as_payload(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhookListener
|
||||
|
||||
mock_task = Mock()
|
||||
mock_task.id = "task_xyz"
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.webhooks.process_agent_webhook"
|
||||
) as mock_process:
|
||||
mock_process.delay.return_value = mock_task
|
||||
with app.test_request_context(
|
||||
"/api/webhooks/agents/tok?event=ping&source=test",
|
||||
method="GET",
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
response = listener.get(
|
||||
webhook_token="tok",
|
||||
agent={"_id": ObjectId()},
|
||||
agent_id_str="agent456",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
call_kwargs = mock_process.delay.call_args[1]
|
||||
assert call_kwargs["payload"]["event"] == "ping"
|
||||
assert call_kwargs["payload"]["source"] == "test"
|
||||
@@ -0,0 +1,406 @@
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSerializeWorkflow:
|
||||
|
||||
def test_serializes_full_workflow(self):
|
||||
from application.api.user.workflows.routes import serialize_workflow
|
||||
|
||||
now = datetime(2024, 6, 15, 10, 30, 0, tzinfo=timezone.utc)
|
||||
doc = {
|
||||
"_id": ObjectId(),
|
||||
"name": "My Workflow",
|
||||
"description": "A test workflow",
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
}
|
||||
result = serialize_workflow(doc)
|
||||
assert result["id"] == str(doc["_id"])
|
||||
assert result["name"] == "My Workflow"
|
||||
assert result["description"] == "A test workflow"
|
||||
assert result["created_at"] == now.isoformat()
|
||||
|
||||
def test_handles_missing_optional_fields(self):
|
||||
from application.api.user.workflows.routes import serialize_workflow
|
||||
|
||||
doc = {"_id": ObjectId()}
|
||||
result = serialize_workflow(doc)
|
||||
assert result["name"] is None
|
||||
assert result["created_at"] is None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSerializeNode:
|
||||
|
||||
def test_serializes_node(self):
|
||||
from application.api.user.workflows.routes import serialize_node
|
||||
|
||||
node = {
|
||||
"id": "node-1",
|
||||
"type": "agent",
|
||||
"title": "Agent Node",
|
||||
"description": "Does things",
|
||||
"position": {"x": 100, "y": 200},
|
||||
"config": {"model": "gpt-4"},
|
||||
}
|
||||
result = serialize_node(node)
|
||||
assert result["id"] == "node-1"
|
||||
assert result["type"] == "agent"
|
||||
assert result["title"] == "Agent Node"
|
||||
assert result["data"] == {"model": "gpt-4"}
|
||||
assert result["position"] == {"x": 100, "y": 200}
|
||||
|
||||
def test_defaults_for_missing_fields(self):
|
||||
from application.api.user.workflows.routes import serialize_node
|
||||
|
||||
node = {"id": "n1", "type": "start"}
|
||||
result = serialize_node(node)
|
||||
assert result["data"] == {}
|
||||
assert result["title"] is None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSerializeEdge:
|
||||
|
||||
def test_serializes_edge(self):
|
||||
from application.api.user.workflows.routes import serialize_edge
|
||||
|
||||
edge = {
|
||||
"id": "edge-1",
|
||||
"source_id": "node-1",
|
||||
"target_id": "node-2",
|
||||
"source_handle": "output",
|
||||
"target_handle": "input",
|
||||
}
|
||||
result = serialize_edge(edge)
|
||||
assert result["id"] == "edge-1"
|
||||
assert result["source"] == "node-1"
|
||||
assert result["target"] == "node-2"
|
||||
assert result["sourceHandle"] == "output"
|
||||
assert result["targetHandle"] == "input"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetWorkflowGraphVersion:
|
||||
|
||||
def test_returns_version(self):
|
||||
from application.api.user.workflows.routes import get_workflow_graph_version
|
||||
|
||||
assert get_workflow_graph_version({"current_graph_version": 3}) == 3
|
||||
|
||||
def test_defaults_to_1(self):
|
||||
from application.api.user.workflows.routes import get_workflow_graph_version
|
||||
|
||||
assert get_workflow_graph_version({}) == 1
|
||||
|
||||
def test_handles_invalid_version(self):
|
||||
from application.api.user.workflows.routes import get_workflow_graph_version
|
||||
|
||||
assert get_workflow_graph_version({"current_graph_version": "bad"}) == 1
|
||||
|
||||
def test_handles_zero_version(self):
|
||||
from application.api.user.workflows.routes import get_workflow_graph_version
|
||||
|
||||
assert get_workflow_graph_version({"current_graph_version": 0}) == 1
|
||||
|
||||
def test_handles_negative_version(self):
|
||||
from application.api.user.workflows.routes import get_workflow_graph_version
|
||||
|
||||
assert get_workflow_graph_version({"current_graph_version": -1}) == 1
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestFetchGraphDocuments:
|
||||
|
||||
def test_returns_versioned_docs(self):
|
||||
from application.api.user.workflows.routes import fetch_graph_documents
|
||||
|
||||
collection = Mock()
|
||||
docs = [{"id": "n1", "graph_version": 2}]
|
||||
collection.find.return_value = docs
|
||||
|
||||
result = fetch_graph_documents(collection, "wf1", 2)
|
||||
assert result == docs
|
||||
collection.find.assert_called_once_with(
|
||||
{"workflow_id": "wf1", "graph_version": 2}
|
||||
)
|
||||
|
||||
def test_falls_back_to_unversioned_for_v1(self):
|
||||
from application.api.user.workflows.routes import fetch_graph_documents
|
||||
|
||||
collection = Mock()
|
||||
unversioned_docs = [{"id": "n1"}]
|
||||
collection.find.side_effect = [[], unversioned_docs]
|
||||
|
||||
result = fetch_graph_documents(collection, "wf1", 1)
|
||||
assert result == unversioned_docs
|
||||
assert collection.find.call_count == 2
|
||||
|
||||
def test_no_fallback_for_higher_versions(self):
|
||||
from application.api.user.workflows.routes import fetch_graph_documents
|
||||
|
||||
collection = Mock()
|
||||
collection.find.return_value = []
|
||||
|
||||
result = fetch_graph_documents(collection, "wf1", 3)
|
||||
assert result == []
|
||||
assert collection.find.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestValidateWorkflowStructure:
|
||||
|
||||
def _make_minimal_workflow(self):
|
||||
nodes = [
|
||||
{"id": "start", "type": "start"},
|
||||
{"id": "end", "type": "end"},
|
||||
]
|
||||
edges = [{"id": "e1", "source": "start", "target": "end"}]
|
||||
return nodes, edges
|
||||
|
||||
def test_valid_minimal_workflow(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
nodes, edges = self._make_minimal_workflow()
|
||||
errors = validate_workflow_structure(nodes, edges)
|
||||
assert errors == []
|
||||
|
||||
def test_empty_nodes(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
errors = validate_workflow_structure([], [])
|
||||
assert any("at least one node" in e for e in errors)
|
||||
|
||||
def test_missing_start_node(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
nodes = [{"id": "end", "type": "end"}]
|
||||
edges = []
|
||||
errors = validate_workflow_structure(nodes, edges)
|
||||
assert any("start node" in e for e in errors)
|
||||
|
||||
def test_missing_end_node(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
nodes = [{"id": "start", "type": "start"}]
|
||||
edges = [{"id": "e1", "source": "start", "target": "somewhere"}]
|
||||
errors = validate_workflow_structure(nodes, edges)
|
||||
assert any("end node" in e for e in errors)
|
||||
|
||||
def test_start_node_without_outgoing_edge(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
nodes = [
|
||||
{"id": "start", "type": "start"},
|
||||
{"id": "end", "type": "end"},
|
||||
]
|
||||
edges = []
|
||||
errors = validate_workflow_structure(nodes, edges)
|
||||
assert any("outgoing edge" in e for e in errors)
|
||||
|
||||
def test_edge_references_nonexistent_node(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
nodes = [
|
||||
{"id": "start", "type": "start"},
|
||||
{"id": "end", "type": "end"},
|
||||
]
|
||||
edges = [{"id": "e1", "source": "start", "target": "ghost"}]
|
||||
errors = validate_workflow_structure(nodes, edges)
|
||||
assert any("non-existent target" in e for e in errors)
|
||||
|
||||
def test_node_without_id(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
nodes = [
|
||||
{"id": "start", "type": "start"},
|
||||
{"type": "end"},
|
||||
]
|
||||
edges = [{"id": "e1", "source": "start", "target": None}]
|
||||
errors = validate_workflow_structure(nodes, edges)
|
||||
assert any("must have an id" in e for e in errors)
|
||||
|
||||
def test_node_without_type(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
nodes = [
|
||||
{"id": "start", "type": "start"},
|
||||
{"id": "end"},
|
||||
]
|
||||
edges = [{"id": "e1", "source": "start", "target": "end"}]
|
||||
errors = validate_workflow_structure(nodes, edges)
|
||||
assert any("must have a type" in e for e in errors)
|
||||
|
||||
def test_condition_node_needs_two_outgoing_edges(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
nodes = [
|
||||
{"id": "start", "type": "start"},
|
||||
{
|
||||
"id": "cond",
|
||||
"type": "condition",
|
||||
"title": "Check",
|
||||
"data": {
|
||||
"cases": [
|
||||
{"expression": "x > 1", "sourceHandle": "case1"},
|
||||
]
|
||||
},
|
||||
},
|
||||
{"id": "end", "type": "end"},
|
||||
]
|
||||
edges = [
|
||||
{"id": "e1", "source": "start", "target": "cond"},
|
||||
{"id": "e2", "source": "cond", "target": "end", "sourceHandle": "else"},
|
||||
]
|
||||
errors = validate_workflow_structure(nodes, edges)
|
||||
assert any("at least 2 outgoing edges" in e for e in errors)
|
||||
|
||||
def test_condition_node_needs_else_branch(self):
|
||||
from application.api.user.workflows.routes import validate_workflow_structure
|
||||
|
||||
nodes = [
|
||||
{"id": "start", "type": "start"},
|
||||
{
|
||||
"id": "cond",
|
||||
"type": "condition",
|
||||
"title": "Check",
|
||||
"data": {
|
||||
"cases": [
|
||||
{"expression": "x > 1", "sourceHandle": "case1"},
|
||||
]
|
||||
},
|
||||
},
|
||||
{"id": "end1", "type": "end"},
|
||||
{"id": "end2", "type": "end"},
|
||||
]
|
||||
edges = [
|
||||
{"id": "e1", "source": "start", "target": "cond"},
|
||||
{"id": "e2", "source": "cond", "target": "end1", "sourceHandle": "case1"},
|
||||
{"id": "e3", "source": "cond", "target": "end2", "sourceHandle": "case1"},
|
||||
]
|
||||
errors = validate_workflow_structure(nodes, edges)
|
||||
assert any("else" in e for e in errors)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestCanReachEnd:
|
||||
|
||||
def test_direct_end_node(self):
|
||||
from application.api.user.workflows.routes import _can_reach_end
|
||||
|
||||
node_map = {"end": {"id": "end", "type": "end"}}
|
||||
assert _can_reach_end("end", [], node_map, {"end"}) is True
|
||||
|
||||
def test_reachable_through_chain(self):
|
||||
from application.api.user.workflows.routes import _can_reach_end
|
||||
|
||||
node_map = {
|
||||
"a": {"id": "a"},
|
||||
"b": {"id": "b"},
|
||||
"end": {"id": "end", "type": "end"},
|
||||
}
|
||||
edges = [
|
||||
{"source": "a", "target": "b"},
|
||||
{"source": "b", "target": "end"},
|
||||
]
|
||||
assert _can_reach_end("a", edges, node_map, {"end"}) is True
|
||||
|
||||
def test_unreachable(self):
|
||||
from application.api.user.workflows.routes import _can_reach_end
|
||||
|
||||
node_map = {
|
||||
"a": {"id": "a"},
|
||||
"b": {"id": "b"},
|
||||
}
|
||||
edges = [{"source": "a", "target": "b"}]
|
||||
assert _can_reach_end("a", edges, node_map, {"end"}) is False
|
||||
|
||||
def test_handles_cycles(self):
|
||||
from application.api.user.workflows.routes import _can_reach_end
|
||||
|
||||
node_map = {"a": {"id": "a"}, "b": {"id": "b"}}
|
||||
edges = [
|
||||
{"source": "a", "target": "b"},
|
||||
{"source": "b", "target": "a"},
|
||||
]
|
||||
assert _can_reach_end("a", edges, node_map, {"end"}) is False
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestValidateJsonSchemaPayload:
|
||||
|
||||
def test_none_input(self):
|
||||
from application.api.user.workflows.routes import validate_json_schema_payload
|
||||
|
||||
result, error = validate_json_schema_payload(None)
|
||||
assert result is None
|
||||
assert error is None
|
||||
|
||||
@patch("application.api.user.workflows.routes.normalize_json_schema_payload")
|
||||
def test_valid_schema(self, mock_normalize):
|
||||
from application.api.user.workflows.routes import validate_json_schema_payload
|
||||
|
||||
mock_normalize.return_value = {"type": "object"}
|
||||
result, error = validate_json_schema_payload({"type": "object"})
|
||||
assert result == {"type": "object"}
|
||||
assert error is None
|
||||
|
||||
@patch("application.api.user.workflows.routes.normalize_json_schema_payload")
|
||||
def test_invalid_schema(self, mock_normalize):
|
||||
from application.api.user.workflows.routes import validate_json_schema_payload
|
||||
from application.core.json_schema_utils import JsonSchemaValidationError
|
||||
|
||||
mock_normalize.side_effect = JsonSchemaValidationError("bad schema")
|
||||
result, error = validate_json_schema_payload({"bad": True})
|
||||
assert result is None
|
||||
assert "bad schema" in error
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestNormalizeAgentNodeJsonSchemas:
|
||||
|
||||
def test_non_agent_nodes_pass_through(self):
|
||||
from application.api.user.workflows.routes import (
|
||||
normalize_agent_node_json_schemas,
|
||||
)
|
||||
|
||||
nodes = [
|
||||
{"id": "n1", "type": "start"},
|
||||
{"id": "n2", "type": "end"},
|
||||
]
|
||||
result = normalize_agent_node_json_schemas(nodes)
|
||||
assert result == nodes
|
||||
|
||||
@patch("application.api.user.workflows.routes.normalize_json_schema_payload")
|
||||
def test_normalizes_agent_node_schema(self, mock_normalize):
|
||||
from application.api.user.workflows.routes import (
|
||||
normalize_agent_node_json_schemas,
|
||||
)
|
||||
|
||||
mock_normalize.return_value = {"type": "object", "properties": {}}
|
||||
nodes = [
|
||||
{
|
||||
"id": "a1",
|
||||
"type": "agent",
|
||||
"data": {"json_schema": {"type": "object"}},
|
||||
}
|
||||
]
|
||||
result = normalize_agent_node_json_schemas(nodes)
|
||||
assert result[0]["data"]["json_schema"] == {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
}
|
||||
|
||||
def test_agent_node_without_schema(self):
|
||||
from application.api.user.workflows.routes import (
|
||||
normalize_agent_node_json_schemas,
|
||||
)
|
||||
|
||||
nodes = [{"id": "a1", "type": "agent", "data": {"model": "gpt-4"}}]
|
||||
result = normalize_agent_node_json_schemas(nodes)
|
||||
assert result[0]["data"] == {"model": "gpt-4"}
|
||||
Reference in new issue
Block a user