mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-14 14:17:08 +00:00
Pg 4 (#2390)
* feat: postgres tests * feat: mongo cutoff * feat: mongo cutoff * feat: adjust docs and compose files * fix: mini code mongo removals * fix: tests and k8s mongo stuff * feat: test fixes * fix: ruff * fix: vale * Potential fix for pull request finding 'CodeQL / Clear-text logging of sensitive information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix: mini suggestions * vale lint fix 2 * fix: codeql columns thing * fix: test mongo * fix: tests coverage * feat: better tests 4 * feat: more tests * feat: decent coverage * fix: ruff fixes * fix: remove mongo mock * feat: enhance workflow engine and API routes; add document retrieval and source handling * feat: e2e tests * fix: mcp, mongo and more * fix: mini codeql warning * fix: agent chunk view * fix: mini issues * fix: more pg fixes * feat: postgres prep on start * feat: qa tests * fix: mini improvements * fix: tests --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Siddhant Rai <siddhant.rai.5686@gmail.com>
This commit is contained in:
312 files changed
+763725
-25159
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,524 @@
|
||||
"""Tests for application/api/user/agents/folders.py.
|
||||
|
||||
Uses the ephemeral ``pg_conn`` fixture to exercise real PG repository code.
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return Flask(__name__)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _patch_folders_db(conn):
|
||||
@contextmanager
|
||||
def _yield():
|
||||
yield conn
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.folders.db_session", _yield
|
||||
), patch(
|
||||
"application.api.user.agents.folders.db_readonly", _yield
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
def _seed_folder(pg_conn, user, name="F", parent_id=None):
|
||||
from application.storage.db.repositories.agent_folders import (
|
||||
AgentFoldersRepository,
|
||||
)
|
||||
return AgentFoldersRepository(pg_conn).create(user, name, parent_id=parent_id)
|
||||
|
||||
|
||||
def _seed_agent(pg_conn, user, folder_id=None):
|
||||
from application.storage.db.repositories.agents import AgentsRepository
|
||||
repo = AgentsRepository(pg_conn)
|
||||
agent = repo.create(user, "test-agent", "published", description="x")
|
||||
if folder_id:
|
||||
repo.set_folder(str(agent["id"]), user, folder_id)
|
||||
return agent
|
||||
|
||||
|
||||
class TestAgentFoldersGet:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
with app.test_request_context("/api/agents/folders/"):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = AgentFolders().get()
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_folders_list(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
user = "u-folders-list"
|
||||
_seed_folder(pg_conn, user, name="A")
|
||||
_seed_folder(pg_conn, user, name="B")
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentFolders().get()
|
||||
assert response.status_code == 200
|
||||
names = [f["name"] for f in response.json["folders"]]
|
||||
assert "A" in names and "B" in names
|
||||
|
||||
|
||||
class TestAgentFoldersPost:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/", method="POST", json={"name": "F"}
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = AgentFolders().post()
|
||||
assert response.status_code == 401
|
||||
|
||||
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": "u"}
|
||||
response = AgentFolders().post()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_creates_folder_at_root(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/", method="POST",
|
||||
json={"name": "Root Folder"},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u-create"}
|
||||
response = AgentFolders().post()
|
||||
assert response.status_code == 201
|
||||
assert response.json["name"] == "Root Folder"
|
||||
assert response.json["parent_id"] is None
|
||||
|
||||
def test_creates_nested_folder(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
user = "u-nested"
|
||||
parent = _seed_folder(pg_conn, user, name="parent")
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/", method="POST",
|
||||
json={"name": "child", "parent_id": str(parent["id"])},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentFolders().post()
|
||||
assert response.status_code == 201
|
||||
assert response.json["parent_id"] == str(parent["id"])
|
||||
|
||||
def test_returns_404_for_missing_parent(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolders
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/", method="POST",
|
||||
json={
|
||||
"name": "child",
|
||||
"parent_id": "00000000-0000-0000-0000-000000000000",
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = AgentFolders().post()
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
class TestAgentFolderGet:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
with app.test_request_context("/api/agents/folders/abc"):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = AgentFolder().get("abc")
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_404_missing_folder(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/00000000-0000-0000-0000-000000000000"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = AgentFolder().get(
|
||||
"00000000-0000-0000-0000-000000000000"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_returns_folder_with_agents_and_subfolders(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
user = "u-folder-detail"
|
||||
parent = _seed_folder(pg_conn, user, name="parent")
|
||||
_seed_folder(pg_conn, user, name="sub", parent_id=str(parent["id"]))
|
||||
_seed_agent(pg_conn, user, folder_id=str(parent["id"]))
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
f"/api/agents/folders/{parent['id']}"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentFolder().get(str(parent["id"]))
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
assert data["name"] == "parent"
|
||||
assert len(data["agents"]) == 1
|
||||
assert len(data["subfolders"]) == 1
|
||||
|
||||
|
||||
class TestAgentFolderPut:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/abc", method="PUT", json={"name": "new"}
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = AgentFolder().put("abc")
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_404_missing_folder(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/00000000-0000-0000-0000-000000000000",
|
||||
method="PUT",
|
||||
json={"name": "new"},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = AgentFolder().put(
|
||||
"00000000-0000-0000-0000-000000000000"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_renames_folder(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
user = "u-rename"
|
||||
folder = _seed_folder(pg_conn, user, name="old")
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
f"/api/agents/folders/{folder['id']}",
|
||||
method="PUT",
|
||||
json={"name": "renamed"},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentFolder().put(str(folder["id"]))
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_prevents_setting_self_as_parent(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
user = "u-self-parent"
|
||||
folder = _seed_folder(pg_conn, user, name="f1")
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
f"/api/agents/folders/{folder['id']}",
|
||||
method="PUT",
|
||||
json={"parent_id": str(folder["id"])},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentFolder().put(str(folder["id"]))
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_sets_parent_to_other_folder(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
user = "u-moveparent"
|
||||
f1 = _seed_folder(pg_conn, user, name="f1")
|
||||
f2 = _seed_folder(pg_conn, user, name="f2")
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
f"/api/agents/folders/{f1['id']}",
|
||||
method="PUT",
|
||||
json={"parent_id": str(f2["id"])},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentFolder().put(str(f1["id"]))
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_returns_404_for_missing_parent(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
user = "u-missingparent"
|
||||
folder = _seed_folder(pg_conn, user, name="f")
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
f"/api/agents/folders/{folder['id']}",
|
||||
method="PUT",
|
||||
json={"parent_id": "00000000-0000-0000-0000-000000000000"},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentFolder().put(str(folder["id"]))
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_clears_parent_id_when_null(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
user = "u-clear"
|
||||
parent = _seed_folder(pg_conn, user, name="p")
|
||||
child = _seed_folder(
|
||||
pg_conn, user, name="c", parent_id=str(parent["id"])
|
||||
)
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
f"/api/agents/folders/{child['id']}",
|
||||
method="PUT",
|
||||
json={"parent_id": None},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentFolder().put(str(child["id"]))
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
class TestAgentFolderDelete:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/abc", method="DELETE"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = AgentFolder().delete("abc")
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_404_missing_folder(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/00000000-0000-0000-0000-000000000000",
|
||||
method="DELETE",
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = AgentFolder().delete(
|
||||
"00000000-0000-0000-0000-000000000000"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_deletes_folder(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import AgentFolder
|
||||
|
||||
user = "u-del"
|
||||
folder = _seed_folder(pg_conn, user, name="tbd")
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
f"/api/agents/folders/{folder['id']}", method="DELETE"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentFolder().delete(str(folder["id"]))
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
class TestMoveAgentToFolder:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.folders import MoveAgentToFolder
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/move_agent",
|
||||
method="POST",
|
||||
json={"agent_id": "x"},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = MoveAgentToFolder().post()
|
||||
assert response.status_code == 401
|
||||
|
||||
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": "u"}
|
||||
response = MoveAgentToFolder().post()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_agent_not_found(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import MoveAgentToFolder
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/move_agent",
|
||||
method="POST",
|
||||
json={"agent_id": "00000000-0000-0000-0000-000000000000"},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = MoveAgentToFolder().post()
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_moves_agent_into_folder(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import MoveAgentToFolder
|
||||
|
||||
user = "u-move"
|
||||
folder = _seed_folder(pg_conn, user, name="target")
|
||||
agent = _seed_agent(pg_conn, user)
|
||||
|
||||
with _patch_folders_db(pg_conn), 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": user}
|
||||
response = MoveAgentToFolder().post()
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_returns_404_folder_not_found(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import MoveAgentToFolder
|
||||
|
||||
user = "u-move-nofolder"
|
||||
agent = _seed_agent(pg_conn, user)
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/move_agent",
|
||||
method="POST",
|
||||
json={
|
||||
"agent_id": str(agent["id"]),
|
||||
"folder_id": "00000000-0000-0000-0000-000000000000",
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = MoveAgentToFolder().post()
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_removes_agent_from_folder(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import MoveAgentToFolder
|
||||
|
||||
user = "u-remove"
|
||||
folder = _seed_folder(pg_conn, user, name="target")
|
||||
agent = _seed_agent(pg_conn, user, folder_id=str(folder["id"]))
|
||||
|
||||
with _patch_folders_db(pg_conn), 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": user}
|
||||
response = MoveAgentToFolder().post()
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
class TestBulkMoveAgents:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.folders import BulkMoveAgents
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/agents/folders/bulk_move",
|
||||
method="POST",
|
||||
json={"agent_ids": ["a"]},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = BulkMoveAgents().post()
|
||||
assert response.status_code == 401
|
||||
|
||||
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": "u"}
|
||||
response = BulkMoveAgents().post()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_bulk_moves_agents(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import BulkMoveAgents
|
||||
|
||||
user = "u-bulk"
|
||||
folder = _seed_folder(pg_conn, user, name="dest")
|
||||
a1 = _seed_agent(pg_conn, user)
|
||||
a2 = _seed_agent(pg_conn, user)
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/bulk_move",
|
||||
method="POST",
|
||||
json={
|
||||
"agent_ids": [str(a1["id"]), str(a2["id"])],
|
||||
"folder_id": str(folder["id"]),
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = BulkMoveAgents().post()
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_returns_404_when_folder_not_found(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import BulkMoveAgents
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/bulk_move",
|
||||
method="POST",
|
||||
json={
|
||||
"agent_ids": ["a"],
|
||||
"folder_id": "00000000-0000-0000-0000-000000000000",
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = BulkMoveAgents().post()
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_bulk_move_tolerates_missing_agents(self, app, pg_conn):
|
||||
from application.api.user.agents.folders import BulkMoveAgents
|
||||
|
||||
user = "u-bulk-partial"
|
||||
folder = _seed_folder(pg_conn, user, name="f")
|
||||
a1 = _seed_agent(pg_conn, user)
|
||||
|
||||
with _patch_folders_db(pg_conn), app.test_request_context(
|
||||
"/api/agents/folders/bulk_move",
|
||||
method="POST",
|
||||
json={
|
||||
"agent_ids": [
|
||||
str(a1["id"]),
|
||||
"00000000-0000-0000-0000-000000000000",
|
||||
],
|
||||
"folder_id": str(folder["id"]),
|
||||
},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = BulkMoveAgents().post()
|
||||
assert response.status_code == 200
|
||||
@@ -0,0 +1,120 @@
|
||||
"""Gap-coverage tests for application.api.user.agents.routes.
|
||||
|
||||
No bson/ObjectId imports. The routes internally use ObjectId (patched where
|
||||
needed) and Mongo collections (mocked). Repository assertions use
|
||||
AgentsRepository patches for Postgres-specific paths.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return Flask(__name__)
|
||||
|
||||
|
||||
def _fake_oid():
|
||||
"""Return a 24-character hex string that looks like a Mongo ObjectId."""
|
||||
return uuid.uuid4().hex[:24]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# normalize_workflow_reference — pure utility, no bson needed
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestNormalizeWorkflowReferenceGaps:
|
||||
pass
|
||||
|
||||
def test_handles_integer_zero(self):
|
||||
from application.api.user.agents.routes import normalize_workflow_reference
|
||||
|
||||
assert normalize_workflow_reference(0) == "0"
|
||||
|
||||
def test_handles_list_converts_to_str(self):
|
||||
from application.api.user.agents.routes import normalize_workflow_reference
|
||||
|
||||
result = normalize_workflow_reference([1, 2])
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_json_string_with_underscore_id(self):
|
||||
from application.api.user.agents.routes import normalize_workflow_reference
|
||||
|
||||
result = normalize_workflow_reference('{"_id": "abc"}')
|
||||
assert result == "abc"
|
||||
|
||||
def test_json_string_with_workflow_id_key(self):
|
||||
from application.api.user.agents.routes import normalize_workflow_reference
|
||||
|
||||
result = normalize_workflow_reference('{"workflow_id": "wf99"}')
|
||||
assert result == "wf99"
|
||||
|
||||
def test_whitespace_only_string_returns_empty(self):
|
||||
from application.api.user.agents.routes import normalize_workflow_reference
|
||||
|
||||
assert normalize_workflow_reference(" \t ") == ""
|
||||
|
||||
def test_dict_missing_all_id_keys_returns_none(self):
|
||||
from application.api.user.agents.routes import normalize_workflow_reference
|
||||
|
||||
result = normalize_workflow_reference({"other_key": "value"})
|
||||
assert result is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# validate_workflow_access — mocked ObjectId + workflows_collection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestValidateWorkflowAccessGaps:
|
||||
pass
|
||||
|
||||
def _fake_objectid(self, valid: bool = True):
|
||||
"""Return a Mock that mimics bson.ObjectId."""
|
||||
oid_cls = Mock()
|
||||
oid_cls.is_valid = Mock(return_value=valid)
|
||||
oid_cls.return_value = Mock()
|
||||
return oid_cls
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# build_agent_document — pure logic, no DB needed
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestBuildAgentDocumentGaps:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GetAgent route — additional edge cases (uuid-based IDs, no bson)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetAgentGaps:
|
||||
pass
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,260 @@
|
||||
"""Tests for helper functions in application/api/user/agents/routes.py."""
|
||||
|
||||
import json
|
||||
from contextlib import contextmanager
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return Flask(__name__)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _patch_db(conn):
|
||||
@contextmanager
|
||||
def _yield():
|
||||
yield conn
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.routes.db_session", _yield
|
||||
), patch(
|
||||
"application.api.user.agents.routes.db_readonly", _yield
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
class TestNormalizeWorkflowReference:
|
||||
def test_none_returns_none(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert normalize_workflow_reference(None) is None
|
||||
|
||||
def test_dict_returns_id_field(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert normalize_workflow_reference({"id": "w1"}) == "w1"
|
||||
|
||||
def test_dict_returns_workflow_id_field(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert (
|
||||
normalize_workflow_reference({"workflow_id": "wf99"}) == "wf99"
|
||||
)
|
||||
|
||||
def test_dict_with_underscore_id(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert normalize_workflow_reference({"_id": "wf-2"}) == "wf-2"
|
||||
|
||||
def test_empty_string_returns_empty(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert normalize_workflow_reference("") == ""
|
||||
|
||||
def test_plain_string_returned(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert normalize_workflow_reference("wf-123") == "wf-123"
|
||||
|
||||
def test_json_string_dict(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert (
|
||||
normalize_workflow_reference(json.dumps({"id": "x"})) == "x"
|
||||
)
|
||||
|
||||
def test_json_string_as_string(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert normalize_workflow_reference('"wf-str"') == "wf-str"
|
||||
|
||||
def test_invalid_json_returns_original(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert normalize_workflow_reference("not-json-wf") == "not-json-wf"
|
||||
|
||||
def test_non_string_non_dict_coerced(self):
|
||||
from application.api.user.agents.routes import (
|
||||
normalize_workflow_reference,
|
||||
)
|
||||
assert normalize_workflow_reference(42) == "42"
|
||||
|
||||
|
||||
class TestResolveWorkflowForUser:
|
||||
def test_none_workflow_returns_none(self, pg_conn):
|
||||
from application.api.user.agents.routes import (
|
||||
_resolve_workflow_for_user,
|
||||
)
|
||||
pg_id, err = _resolve_workflow_for_user(pg_conn, None, "u")
|
||||
assert pg_id is None and err is None
|
||||
|
||||
def test_not_found_returns_error(self, pg_conn, app):
|
||||
from application.api.user.agents.routes import (
|
||||
_resolve_workflow_for_user,
|
||||
)
|
||||
with app.app_context():
|
||||
pg_id, err = _resolve_workflow_for_user(
|
||||
pg_conn,
|
||||
"00000000-0000-0000-0000-000000000000",
|
||||
"u",
|
||||
)
|
||||
assert pg_id is None
|
||||
assert err is not None
|
||||
assert err.status_code == 404
|
||||
|
||||
def test_resolves_owned_workflow(self, pg_conn, app):
|
||||
from application.api.user.agents.routes import (
|
||||
_resolve_workflow_for_user,
|
||||
)
|
||||
from application.storage.db.repositories.workflows import (
|
||||
WorkflowsRepository,
|
||||
)
|
||||
|
||||
user = "owner"
|
||||
wf = WorkflowsRepository(pg_conn).create(user, "wf")
|
||||
with app.app_context():
|
||||
pg_id, err = _resolve_workflow_for_user(
|
||||
pg_conn, str(wf["id"]), user,
|
||||
)
|
||||
assert pg_id == str(wf["id"])
|
||||
assert err is None
|
||||
|
||||
|
||||
class TestResolveFolderId:
|
||||
def test_none_returns_none(self, pg_conn):
|
||||
from application.api.user.agents.routes import _resolve_folder_id
|
||||
pg_id, err = _resolve_folder_id(pg_conn, None, "u")
|
||||
assert pg_id is None
|
||||
assert err is None
|
||||
|
||||
def test_not_found_returns_error(self, pg_conn, app):
|
||||
from application.api.user.agents.routes import _resolve_folder_id
|
||||
|
||||
with app.app_context():
|
||||
pg_id, err = _resolve_folder_id(
|
||||
pg_conn, "00000000-0000-0000-0000-000000000000", "u",
|
||||
)
|
||||
assert pg_id is None
|
||||
assert err.status_code == 404
|
||||
|
||||
def test_resolves_owned_folder(self, pg_conn, app):
|
||||
from application.api.user.agents.routes import _resolve_folder_id
|
||||
from application.storage.db.repositories.agent_folders import (
|
||||
AgentFoldersRepository,
|
||||
)
|
||||
|
||||
user = "u"
|
||||
folder = AgentFoldersRepository(pg_conn).create(user, "F")
|
||||
with app.app_context():
|
||||
pg_id, err = _resolve_folder_id(pg_conn, str(folder["id"]), user)
|
||||
assert pg_id == str(folder["id"])
|
||||
assert err is None
|
||||
|
||||
|
||||
class TestFormatAgentOutput:
|
||||
def test_basic_shape(self):
|
||||
from application.api.user.agents.routes import _format_agent_output
|
||||
|
||||
agent = {
|
||||
"id": "agent-1",
|
||||
"name": "My",
|
||||
"description": "D",
|
||||
"source_id": "src-1",
|
||||
"extra_source_ids": ["s1", "s2"],
|
||||
"chunks": 4,
|
||||
"retriever": "classic",
|
||||
"prompt_id": "p-1",
|
||||
"tools": [],
|
||||
"agent_type": "classic",
|
||||
"status": "draft",
|
||||
"key": "secret-api-key-long",
|
||||
}
|
||||
out = _format_agent_output(agent)
|
||||
assert out["id"] == "agent-1"
|
||||
assert out["name"] == "My"
|
||||
assert out["source"] == "src-1"
|
||||
assert out["sources"] == ["s1", "s2"]
|
||||
# masked key: first4...last4
|
||||
assert out["key"].startswith("secr") and out["key"].endswith("long")
|
||||
|
||||
def test_no_key_masking(self):
|
||||
from application.api.user.agents.routes import _format_agent_output
|
||||
|
||||
agent = {"id": "a", "name": "n", "chunks": None}
|
||||
out = _format_agent_output(agent, include_key_masked=False)
|
||||
assert "key" not in out
|
||||
|
||||
def test_empty_key_returns_empty_string(self):
|
||||
from application.api.user.agents.routes import _format_agent_output
|
||||
|
||||
agent = {"id": "a", "name": "n", "key": ""}
|
||||
out = _format_agent_output(agent)
|
||||
assert out["key"] == ""
|
||||
|
||||
def test_with_folder_and_workflow(self):
|
||||
from application.api.user.agents.routes import _format_agent_output
|
||||
|
||||
agent = {
|
||||
"id": "a", "name": "n",
|
||||
"folder_id": "f-1", "workflow_id": "w-1",
|
||||
}
|
||||
out = _format_agent_output(agent)
|
||||
assert out["folder_id"] == "f-1"
|
||||
assert out["workflow"] == "w-1"
|
||||
|
||||
|
||||
class TestBuildCreateKwargs:
|
||||
def test_classic_kwargs(self):
|
||||
from application.api.user.agents.routes import _build_create_kwargs
|
||||
|
||||
data = {
|
||||
"description": "d",
|
||||
"agent_type": "classic",
|
||||
"chunks": "3",
|
||||
"retriever": "classic",
|
||||
}
|
||||
out = _build_create_kwargs(
|
||||
data, image_url="", agent_type="classic",
|
||||
)
|
||||
assert out["description"] == "d"
|
||||
assert out["chunks"] == 3
|
||||
|
||||
def test_invalid_chunks_skipped(self, app):
|
||||
from application.api.user.agents.routes import _build_create_kwargs
|
||||
|
||||
data = {"chunks": "abc"}
|
||||
with app.app_context():
|
||||
out = _build_create_kwargs(
|
||||
data, image_url="", agent_type="classic",
|
||||
)
|
||||
assert "chunks" not in out
|
||||
|
||||
def test_prompt_id_default_not_set(self):
|
||||
from application.api.user.agents.routes import _build_create_kwargs
|
||||
|
||||
data = {"prompt_id": "default"}
|
||||
out = _build_create_kwargs(
|
||||
data, image_url="", agent_type="classic",
|
||||
)
|
||||
assert "prompt_id" not in out
|
||||
|
||||
def test_image_url_used_when_provided(self):
|
||||
from application.api.user.agents.routes import _build_create_kwargs
|
||||
|
||||
out = _build_create_kwargs(
|
||||
{}, image_url="/upload/img.png", agent_type="classic",
|
||||
)
|
||||
assert out.get("image") == "/upload/img.png"
|
||||
@@ -0,0 +1,290 @@
|
||||
"""Tests for application/api/user/agents/sharing.py.
|
||||
|
||||
Uses the ephemeral ``pg_conn`` fixture to exercise the real PG repository
|
||||
code paths (agents, users).
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return Flask(__name__)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _patch_db(conn):
|
||||
@contextmanager
|
||||
def _yield():
|
||||
yield conn
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.db_session", _yield
|
||||
), patch(
|
||||
"application.api.user.agents.sharing.db_readonly", _yield
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
def _make_agent(pg_conn, user_id="owner", *, shared=False, shared_token=None):
|
||||
from application.storage.db.repositories.agents import AgentsRepository
|
||||
agent = AgentsRepository(pg_conn).create(
|
||||
user_id,
|
||||
"Agent",
|
||||
"published",
|
||||
description="desc",
|
||||
)
|
||||
if shared:
|
||||
AgentsRepository(pg_conn).update(
|
||||
str(agent["id"]), user_id,
|
||||
{"shared": True, "shared_token": shared_token},
|
||||
)
|
||||
agent = AgentsRepository(pg_conn).get(str(agent["id"]), user_id)
|
||||
return agent
|
||||
|
||||
|
||||
class TestSharedAgentGet:
|
||||
def test_returns_400_missing_token(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
with app.test_request_context("/api/shared_agent"):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_for_unknown_token(self, app, pg_conn):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
"/api/shared_agent?token=unknown"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_returns_agent_for_known_token(self, app, pg_conn):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
_make_agent(pg_conn, shared=True, shared_token="abc123")
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
"/api/shared_agent?token=abc123"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
assert data["shared_token"] == "abc123"
|
||||
assert data["shared"] is True
|
||||
|
||||
def test_records_shared_with_different_user(self, app, pg_conn):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
_make_agent(pg_conn, user_id="owner", shared=True, shared_token="tk1")
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
"/api/shared_agent?token=tk1"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "other-user"}
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_db_error_returns_400(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgent
|
||||
|
||||
@contextmanager
|
||||
def _broken():
|
||||
raise RuntimeError("boom")
|
||||
yield
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.db_readonly", _broken
|
||||
), app.test_request_context("/api/shared_agent?token=x"):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = SharedAgent().get()
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
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_empty_list_for_new_user(self, app, pg_conn):
|
||||
from application.api.user.agents.sharing import SharedAgents
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
"/api/shared_agents"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "new-user"}
|
||||
response = SharedAgents().get()
|
||||
assert response.status_code == 200
|
||||
assert response.json == []
|
||||
|
||||
def test_returns_shared_agents_for_user(self, app, pg_conn):
|
||||
"""After SharedAgent adds an agent to the user's shared_with_me, it
|
||||
should appear in SharedAgents."""
|
||||
from application.api.user.agents.sharing import SharedAgent, SharedAgents
|
||||
|
||||
_make_agent(pg_conn, user_id="owner", shared=True, shared_token="tk2")
|
||||
|
||||
# Trigger add-shared flow via SharedAgent
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
"/api/shared_agent?token=tk2"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "viewer-user"}
|
||||
SharedAgent().get()
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
"/api/shared_agents"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "viewer-user"}
|
||||
response = SharedAgents().get()
|
||||
assert response.status_code == 200
|
||||
assert len(response.json) == 1
|
||||
assert response.json[0]["shared"] is True
|
||||
|
||||
def test_db_error_returns_400(self, app):
|
||||
from application.api.user.agents.sharing import SharedAgents
|
||||
|
||||
@contextmanager
|
||||
def _broken():
|
||||
raise RuntimeError("boom")
|
||||
yield
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.db_session", _broken
|
||||
), app.test_request_context("/api/shared_agents"):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = SharedAgents().get()
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
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": "x", "shared": True}
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = None
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 401
|
||||
|
||||
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": "u"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_400_missing_shared(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
with app.test_request_context(
|
||||
"/api/share_agent", method="PUT", json={"id": "x"}
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_agent_not_found(self, app, pg_conn):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={"id": "00000000-0000-0000-0000-000000000000", "shared": True},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_shares_agent(self, app, pg_conn):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
from application.storage.db.repositories.agents import AgentsRepository
|
||||
|
||||
agent = _make_agent(pg_conn, user_id="owner")
|
||||
agent_id = str(agent["id"])
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={"id": agent_id, "shared": True, "username": "alice"},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "owner"}
|
||||
response = ShareAgent().put()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["success"] is True
|
||||
assert response.json["shared_token"] # non-empty token generated
|
||||
got = AgentsRepository(pg_conn).get(agent_id, "owner")
|
||||
assert got["shared"] is True
|
||||
assert got["shared_token"]
|
||||
assert got["shared_metadata"]["shared_by"] == "alice"
|
||||
|
||||
def test_unshares_agent(self, app, pg_conn):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
from application.storage.db.repositories.agents import AgentsRepository
|
||||
|
||||
agent = _make_agent(pg_conn, user_id="owner", shared=True, shared_token="tk")
|
||||
agent_id = str(agent["id"])
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={"id": agent_id, "shared": False},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "owner"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 200
|
||||
got = AgentsRepository(pg_conn).get(agent_id, "owner")
|
||||
assert got["shared"] is False
|
||||
assert got["shared_token"] is None
|
||||
|
||||
def test_db_error_returns_400(self, app):
|
||||
from application.api.user.agents.sharing import ShareAgent
|
||||
|
||||
@contextmanager
|
||||
def _broken():
|
||||
raise RuntimeError("boom")
|
||||
yield
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.sharing.db_session", _broken
|
||||
), app.test_request_context(
|
||||
"/api/share_agent",
|
||||
method="PUT",
|
||||
json={"id": "x", "shared": True},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = ShareAgent().put()
|
||||
assert response.status_code == 400
|
||||
@@ -0,0 +1,296 @@
|
||||
"""Gap-coverage tests for application.api.user.agents.webhooks.
|
||||
|
||||
These tests use only stdlib IDs (uuid / hex strings) — no bson/ObjectId.
|
||||
The agent routes still read from Mongo collections internally; we mock
|
||||
those collection objects so the tests run without pymongo installed.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from contextlib import contextmanager
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
|
||||
def _fake_oid():
|
||||
"""24-character hex string used as a substitute for a Mongo ObjectId string."""
|
||||
return uuid.uuid4().hex[:24]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return Flask(__name__)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AgentWebhook.get — additional coverage beyond test_webhooks.py
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentWebhookGetGaps:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AgentWebhookListener — additional POST / GET edge cases
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAgentWebhookListenerGaps:
|
||||
pass
|
||||
|
||||
def test_post_empty_payload_still_enqueues(self, app):
|
||||
"""Empty dict payload does not block task enqueue (warning only)."""
|
||||
from application.api.user.agents.webhooks import AgentWebhookListener
|
||||
|
||||
mock_task = Mock()
|
||||
mock_task.id = "task_empty"
|
||||
|
||||
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={},
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
response = listener._enqueue_webhook_task("agent1", {}, "POST")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json["task_id"] == "task_empty"
|
||||
|
||||
def test_get_empty_query_string(self, app):
|
||||
"""GET request with no query params produces an empty payload dict."""
|
||||
from application.api.user.agents.webhooks import AgentWebhookListener
|
||||
|
||||
mock_task = Mock()
|
||||
mock_task.id = "task_noqs"
|
||||
|
||||
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="GET",
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
response = listener.get(
|
||||
webhook_token="tok",
|
||||
agent={"_id": uuid.uuid4().hex},
|
||||
agent_id_str="agentXYZ",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
call_kwargs = mock_process.delay.call_args[1]
|
||||
assert call_kwargs["payload"] == {}
|
||||
|
||||
def test_enqueue_returns_task_id_in_response(self, app):
|
||||
"""Success response body includes task_id from the Celery task."""
|
||||
from application.api.user.agents.webhooks import AgentWebhookListener
|
||||
|
||||
mock_task = Mock()
|
||||
mock_task.id = "celery-task-99"
|
||||
|
||||
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={"k": "v"},
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
response = listener._enqueue_webhook_task("a1", {"k": "v"}, "POST")
|
||||
|
||||
assert response.json["success"] is True
|
||||
assert response.json["task_id"] == "celery-task-99"
|
||||
|
||||
def test_enqueue_error_returns_500_with_message(self, app):
|
||||
"""Queue failure returns 500 with a human-readable message."""
|
||||
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 = RuntimeError("celery is down")
|
||||
with app.test_request_context(
|
||||
"/api/webhooks/agents/tok",
|
||||
method="POST",
|
||||
json={"x": 1},
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
response = listener._enqueue_webhook_task("a1", {"x": 1}, "POST")
|
||||
|
||||
assert response.status_code == 500
|
||||
assert response.json["success"] is False
|
||||
assert "message" in response.json
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Real-PG tests for AgentWebhook (get) and AgentWebhookListener
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _patch_webhooks_db(conn):
|
||||
@contextmanager
|
||||
def _yield():
|
||||
yield conn
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.webhooks.db_session", _yield
|
||||
), patch(
|
||||
"application.api.user.agents.webhooks.db_readonly", _yield
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _patch_base_db(conn):
|
||||
@contextmanager
|
||||
def _yield():
|
||||
yield conn
|
||||
|
||||
with patch(
|
||||
"application.api.user.base.db_readonly", _yield
|
||||
), patch(
|
||||
"application.api.user.base.db_session", _yield
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
class TestAgentWebhookGet:
|
||||
def test_returns_401_unauthenticated(self, app):
|
||||
from application.api.user.agents.webhooks import AgentWebhook
|
||||
|
||||
with app.test_request_context("/api/agent_webhook?id=x"):
|
||||
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": "u"}
|
||||
response = AgentWebhook().get()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_missing_agent(self, app, pg_conn):
|
||||
from application.api.user.agents.webhooks import AgentWebhook
|
||||
|
||||
with _patch_webhooks_db(pg_conn), app.test_request_context(
|
||||
"/api/agent_webhook?id=00000000-0000-0000-0000-000000000000"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = AgentWebhook().get()
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_generates_webhook_url(self, app, pg_conn):
|
||||
from application.api.user.agents.webhooks import AgentWebhook
|
||||
from application.storage.db.repositories.agents import AgentsRepository
|
||||
|
||||
user = "u-wh"
|
||||
agent = AgentsRepository(pg_conn).create(user, "a", "published")
|
||||
|
||||
with _patch_webhooks_db(pg_conn), patch(
|
||||
"application.api.user.agents.webhooks.settings.API_URL",
|
||||
"https://api.test",
|
||||
), app.test_request_context(
|
||||
f"/api/agent_webhook?id={agent['id']}"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentWebhook().get()
|
||||
assert response.status_code == 200
|
||||
assert response.json["webhook_url"].startswith(
|
||||
"https://api.test/api/webhooks/agents/"
|
||||
)
|
||||
|
||||
def test_reuses_existing_webhook_token(self, app, pg_conn):
|
||||
from application.api.user.agents.webhooks import AgentWebhook
|
||||
from application.storage.db.repositories.agents import AgentsRepository
|
||||
|
||||
user = "u-wh-reuse"
|
||||
agent = AgentsRepository(pg_conn).create(
|
||||
user, "a", "published", incoming_webhook_token="existing-tok",
|
||||
)
|
||||
|
||||
with _patch_webhooks_db(pg_conn), patch(
|
||||
"application.api.user.agents.webhooks.settings.API_URL",
|
||||
"https://api.test",
|
||||
), app.test_request_context(
|
||||
f"/api/agent_webhook?id={agent['id']}"
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": user}
|
||||
response = AgentWebhook().get()
|
||||
assert "existing-tok" in response.json["webhook_url"]
|
||||
|
||||
|
||||
class TestAgentWebhookListener:
|
||||
def test_post_valid_enqueues_task(self, app, pg_conn):
|
||||
from application.api.user.agents.webhooks import AgentWebhookListener
|
||||
from application.storage.db.repositories.agents import AgentsRepository
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
user = "u-wh-enq"
|
||||
agent = AgentsRepository(pg_conn).create(
|
||||
user, "a", "published", incoming_webhook_token="tk-enq",
|
||||
)
|
||||
fake_task = MagicMock(id="task-post")
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.webhooks.process_agent_webhook.delay",
|
||||
return_value=fake_task,
|
||||
), app.test_request_context(
|
||||
"/api/webhooks/agents/tk-enq", method="POST",
|
||||
json={"event": "x"},
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
# Call directly with manually-injected kwargs (bypass decorator)
|
||||
response = listener.post(
|
||||
webhook_token="tk-enq",
|
||||
agent=agent,
|
||||
agent_id_str=str(agent["id"]),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json["task_id"] == "task-post"
|
||||
|
||||
def test_get_collects_query_params(self, app, pg_conn):
|
||||
from application.api.user.agents.webhooks import AgentWebhookListener
|
||||
from application.storage.db.repositories.agents import AgentsRepository
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
user = "u-wh-get"
|
||||
agent = AgentsRepository(pg_conn).create(
|
||||
user, "a", "published", incoming_webhook_token="tk-get",
|
||||
)
|
||||
fake_task = MagicMock(id="task-get")
|
||||
|
||||
with patch(
|
||||
"application.api.user.agents.webhooks.process_agent_webhook.delay",
|
||||
return_value=fake_task,
|
||||
), app.test_request_context(
|
||||
"/api/webhooks/agents/tk-get?foo=bar&baz=42"
|
||||
):
|
||||
listener = AgentWebhookListener()
|
||||
response = listener.get(
|
||||
webhook_token="tk-get",
|
||||
agent=agent,
|
||||
agent_id_str=str(agent["id"]),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
Reference in new issue
Block a user