refactor: rename the application package to docsgpt

The backend import package is now docsgpt, the name it will carry on PyPI;
application was far too generic to install into anyone's site-packages.
git mv plus a mechanical rewrite of every import, dotted string and path
reference: 734 Python files, the compose files, Dockerfile, workflows, docs,
setup scripts, devcontainer, k8s manifests, vscode config, pytest and coverage
config, .gitignore. Behaviour is unchanged.

Kept for one release:
- A top-level application package whose meta-path finder resolves
  application.x.y to the already-imported docsgpt.x.y object, so old imports
  and entry points (celery -A application.app.celery,
  uvicorn application.asgi:asgi_app) keep working with a FutureWarning.
- Celery registers every application.* task name as an alias of its
  docsgpt.* task on start-up, so messages queued by the previous release still
  run. The redbeat key prefix moves to redbeat:docsgpt:v2: so schedule entries
  the previous release wrote are left unread instead of firing twice.

The backend image builds from the repository root (docker build -f
docsgpt/Dockerfile .) so it can ship the alias package; a root .dockerignore
allow-lists docsgpt/ and application/ and keeps caches, local data, .env
files, the sample index files and the Dockerfile out. Compose and the image
workflows point at the new context.
This commit is contained in:
Alex committed 2026-09-07 10:20:43 +01:00
1 parent 3e7f1f91b4
commit 574f96341e
984 files changed
+9847 -9682

No files matched your search

+36 -36
View File
@@ -1,4 +1,4 @@
"""Tests for application/api/user/agents/folders.py.
"""Tests for docsgpt/api/user/agents/folders.py.
Uses the ephemeral ``pg_conn`` fixture to exercise real PG repository code.
"""
@@ -22,22 +22,22 @@ def _patch_folders_db(conn):
yield conn
with patch(
"application.api.user.agents.folders.db_session", _yield
"docsgpt.api.user.agents.folders.db_session", _yield
), patch(
"application.api.user.agents.folders.db_readonly", _yield
"docsgpt.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 (
from docsgpt.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
from docsgpt.storage.db.repositories.agents import AgentsRepository
repo = AgentsRepository(pg_conn)
agent = repo.create(user, "test-agent", "published", description="x")
if folder_id:
@@ -47,7 +47,7 @@ def _seed_agent(pg_conn, user, folder_id=None):
class TestAgentFoldersGet:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.folders import AgentFolders
from docsgpt.api.user.agents.folders import AgentFolders
with app.test_request_context("/api/agents/folders/"):
from flask import request
@@ -56,7 +56,7 @@ class TestAgentFoldersGet:
assert response.status_code == 401
def test_returns_folders_list(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolders
from docsgpt.api.user.agents.folders import AgentFolders
user = "u-folders-list"
_seed_folder(pg_conn, user, name="A")
@@ -75,7 +75,7 @@ class TestAgentFoldersGet:
class TestAgentFoldersPost:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.folders import AgentFolders
from docsgpt.api.user.agents.folders import AgentFolders
with app.test_request_context(
"/api/agents/folders/", method="POST", json={"name": "F"}
@@ -86,7 +86,7 @@ class TestAgentFoldersPost:
assert response.status_code == 401
def test_returns_400_missing_name(self, app):
from application.api.user.agents.folders import AgentFolders
from docsgpt.api.user.agents.folders import AgentFolders
with app.test_request_context(
"/api/agents/folders/", method="POST", json={}
@@ -97,7 +97,7 @@ class TestAgentFoldersPost:
assert response.status_code == 400
def test_creates_folder_at_root(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolders
from docsgpt.api.user.agents.folders import AgentFolders
with _patch_folders_db(pg_conn), app.test_request_context(
"/api/agents/folders/", method="POST",
@@ -111,7 +111,7 @@ class TestAgentFoldersPost:
assert response.json["parent_id"] is None
def test_creates_nested_folder(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolders
from docsgpt.api.user.agents.folders import AgentFolders
user = "u-nested"
parent = _seed_folder(pg_conn, user, name="parent")
@@ -127,7 +127,7 @@ class TestAgentFoldersPost:
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
from docsgpt.api.user.agents.folders import AgentFolders
with _patch_folders_db(pg_conn), app.test_request_context(
"/api/agents/folders/", method="POST",
@@ -144,7 +144,7 @@ class TestAgentFoldersPost:
class TestAgentFolderGet:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
with app.test_request_context("/api/agents/folders/abc"):
from flask import request
@@ -153,7 +153,7 @@ class TestAgentFolderGet:
assert response.status_code == 401
def test_returns_404_missing_folder(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
with _patch_folders_db(pg_conn), app.test_request_context(
"/api/agents/folders/00000000-0000-0000-0000-000000000000"
@@ -166,7 +166,7 @@ class TestAgentFolderGet:
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
from docsgpt.api.user.agents.folders import AgentFolder
user = "u-folder-detail"
parent = _seed_folder(pg_conn, user, name="parent")
@@ -188,7 +188,7 @@ class TestAgentFolderGet:
class TestAgentFolderPut:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
with app.test_request_context(
"/api/agents/folders/abc", method="PUT", json={"name": "new"}
@@ -199,7 +199,7 @@ class TestAgentFolderPut:
assert response.status_code == 401
def test_returns_404_missing_folder(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
with _patch_folders_db(pg_conn), app.test_request_context(
"/api/agents/folders/00000000-0000-0000-0000-000000000000",
@@ -214,7 +214,7 @@ class TestAgentFolderPut:
assert response.status_code == 404
def test_renames_folder(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
user = "u-rename"
folder = _seed_folder(pg_conn, user, name="old")
@@ -230,7 +230,7 @@ class TestAgentFolderPut:
assert response.status_code == 200
def test_prevents_setting_self_as_parent(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
user = "u-self-parent"
folder = _seed_folder(pg_conn, user, name="f1")
@@ -246,7 +246,7 @@ class TestAgentFolderPut:
assert response.status_code == 400
def test_sets_parent_to_other_folder(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
user = "u-moveparent"
f1 = _seed_folder(pg_conn, user, name="f1")
@@ -263,7 +263,7 @@ class TestAgentFolderPut:
assert response.status_code == 200
def test_returns_404_for_missing_parent(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
user = "u-missingparent"
folder = _seed_folder(pg_conn, user, name="f")
@@ -279,7 +279,7 @@ class TestAgentFolderPut:
assert response.status_code == 404
def test_clears_parent_id_when_null(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
user = "u-clear"
parent = _seed_folder(pg_conn, user, name="p")
@@ -300,7 +300,7 @@ class TestAgentFolderPut:
class TestAgentFolderDelete:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
with app.test_request_context(
"/api/agents/folders/abc", method="DELETE"
@@ -311,7 +311,7 @@ class TestAgentFolderDelete:
assert response.status_code == 401
def test_returns_404_missing_folder(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
with _patch_folders_db(pg_conn), app.test_request_context(
"/api/agents/folders/00000000-0000-0000-0000-000000000000",
@@ -325,7 +325,7 @@ class TestAgentFolderDelete:
assert response.status_code == 404
def test_deletes_folder(self, app, pg_conn):
from application.api.user.agents.folders import AgentFolder
from docsgpt.api.user.agents.folders import AgentFolder
user = "u-del"
folder = _seed_folder(pg_conn, user, name="tbd")
@@ -341,7 +341,7 @@ class TestAgentFolderDelete:
class TestMoveAgentToFolder:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.folders import MoveAgentToFolder
from docsgpt.api.user.agents.folders import MoveAgentToFolder
with app.test_request_context(
"/api/agents/folders/move_agent",
@@ -354,7 +354,7 @@ class TestMoveAgentToFolder:
assert response.status_code == 401
def test_returns_400_missing_agent_id(self, app):
from application.api.user.agents.folders import MoveAgentToFolder
from docsgpt.api.user.agents.folders import MoveAgentToFolder
with app.test_request_context(
"/api/agents/folders/move_agent",
@@ -367,7 +367,7 @@ class TestMoveAgentToFolder:
assert response.status_code == 400
def test_returns_404_agent_not_found(self, app, pg_conn):
from application.api.user.agents.folders import MoveAgentToFolder
from docsgpt.api.user.agents.folders import MoveAgentToFolder
with _patch_folders_db(pg_conn), app.test_request_context(
"/api/agents/folders/move_agent",
@@ -380,7 +380,7 @@ class TestMoveAgentToFolder:
assert response.status_code == 404
def test_moves_agent_into_folder(self, app, pg_conn):
from application.api.user.agents.folders import MoveAgentToFolder
from docsgpt.api.user.agents.folders import MoveAgentToFolder
user = "u-move"
folder = _seed_folder(pg_conn, user, name="target")
@@ -400,7 +400,7 @@ class TestMoveAgentToFolder:
assert response.status_code == 200
def test_returns_404_folder_not_found(self, app, pg_conn):
from application.api.user.agents.folders import MoveAgentToFolder
from docsgpt.api.user.agents.folders import MoveAgentToFolder
user = "u-move-nofolder"
agent = _seed_agent(pg_conn, user)
@@ -419,7 +419,7 @@ class TestMoveAgentToFolder:
assert response.status_code == 404
def test_removes_agent_from_folder(self, app, pg_conn):
from application.api.user.agents.folders import MoveAgentToFolder
from docsgpt.api.user.agents.folders import MoveAgentToFolder
user = "u-remove"
folder = _seed_folder(pg_conn, user, name="target")
@@ -438,7 +438,7 @@ class TestMoveAgentToFolder:
class TestBulkMoveAgents:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.folders import BulkMoveAgents
from docsgpt.api.user.agents.folders import BulkMoveAgents
with app.test_request_context(
"/api/agents/folders/bulk_move",
@@ -451,7 +451,7 @@ class TestBulkMoveAgents:
assert response.status_code == 401
def test_returns_400_missing_agent_ids(self, app):
from application.api.user.agents.folders import BulkMoveAgents
from docsgpt.api.user.agents.folders import BulkMoveAgents
with app.test_request_context(
"/api/agents/folders/bulk_move",
@@ -464,7 +464,7 @@ class TestBulkMoveAgents:
assert response.status_code == 400
def test_bulk_moves_agents(self, app, pg_conn):
from application.api.user.agents.folders import BulkMoveAgents
from docsgpt.api.user.agents.folders import BulkMoveAgents
user = "u-bulk"
folder = _seed_folder(pg_conn, user, name="dest")
@@ -485,7 +485,7 @@ class TestBulkMoveAgents:
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
from docsgpt.api.user.agents.folders import BulkMoveAgents
with _patch_folders_db(pg_conn), app.test_request_context(
"/api/agents/folders/bulk_move",
@@ -501,7 +501,7 @@ class TestBulkMoveAgents:
assert response.status_code == 404
def test_bulk_move_tolerates_missing_agents(self, app, pg_conn):
from application.api.user.agents.folders import BulkMoveAgents
from docsgpt.api.user.agents.folders import BulkMoveAgents
user = "u-bulk-partial"
folder = _seed_folder(pg_conn, user, name="f")
+16 -16
View File
@@ -1,4 +1,4 @@
"""Tests for application/api/user/agents/guardrails.py and config validation.
"""Tests for docsgpt/api/user/agents/guardrails.py and config validation.
Uses the ephemeral ``pg_conn`` fixture so the repository code is real.
"""
@@ -22,13 +22,13 @@ def _patch_db(conn):
yield conn
with patch(
"application.api.user.agents.guardrails.db_readonly", _yield
"docsgpt.api.user.agents.guardrails.db_readonly", _yield
):
yield
def _seed_agent(pg_conn, user="u-gr"):
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.agents import AgentsRepository
return AgentsRepository(pg_conn).create(user, "guarded", "published")
@@ -41,7 +41,7 @@ def _seed_agent(pg_conn, user="u-gr"):
@pytest.mark.unit
class TestNormalizeAgentConfig:
def _norm(self, raw):
from application.api.user.agents.routes import normalize_agent_config
from docsgpt.api.user.agents.routes import normalize_agent_config
return normalize_agent_config(raw)
@@ -140,7 +140,7 @@ class TestNormalizeAgentConfig:
@pytest.mark.unit
class TestCatalogRoute:
def _get(self, app, decoded_token={"sub": "u-gr"}):
from application.api.user.agents.guardrails import GuardrailCatalog
from docsgpt.api.user.agents.guardrails import GuardrailCatalog
with app.test_request_context("/api/guardrails/catalog"):
from flask import request
@@ -183,7 +183,7 @@ class TestCatalogRoute:
def test_reports_the_instance_floor(self, app, monkeypatch):
import json
from application.core.settings import settings
from docsgpt.core.settings import settings
monkeypatch.setattr(
settings,
@@ -199,7 +199,7 @@ class TestCatalogRoute:
def test_floor_is_null_when_unset(self, app, monkeypatch):
import json
from application.core.settings import settings
from docsgpt.core.settings import settings
monkeypatch.setattr(settings, "GUARDRAILS_FLOOR", {})
payload = json.loads(self._get(app).get_data(as_text=True))
@@ -214,7 +214,7 @@ class TestCatalogRoute:
@pytest.mark.unit
class TestEventsRoute:
def _get(self, app, pg_conn, args="", decoded_token={"sub": "u-gr"}):
from application.api.user.agents.guardrails import GuardrailEvents
from docsgpt.api.user.agents.guardrails import GuardrailEvents
with app.test_request_context(f"/api/guardrails/events{args}"):
from flask import request
@@ -248,7 +248,7 @@ class TestEventsRoute:
def test_returns_recorded_events(self, app, pg_conn):
import json
from application.storage.db.repositories.guardrail_events import (
from docsgpt.storage.db.repositories.guardrail_events import (
GuardrailEventsRepository,
)
@@ -277,7 +277,7 @@ class TestEventsRoute:
@pytest.mark.unit
class TestSummaryRoute:
def _get(self, app, pg_conn, args="", decoded_token={"sub": "u-gr"}):
from application.api.user.agents.guardrails import GuardrailSummary
from docsgpt.api.user.agents.guardrails import GuardrailSummary
with app.test_request_context(f"/api/guardrails/summary{args}"):
from flask import request
@@ -293,7 +293,7 @@ class TestSummaryRoute:
def test_splits_blocked_from_flagged_from_unevaluated(self, app, pg_conn):
import json
from application.storage.db.repositories.guardrail_events import (
from docsgpt.storage.db.repositories.guardrail_events import (
GuardrailEventsRepository,
)
@@ -331,7 +331,7 @@ class TestSummaryAgentScoping:
"""The agent-logs panel needs per-agent aggregates, not per-user ones."""
def _get(self, app, pg_conn, args="", decoded_token={"sub": "u-gr"}):
from application.api.user.agents.guardrails import GuardrailSummary
from docsgpt.api.user.agents.guardrails import GuardrailSummary
with app.test_request_context(f"/api/guardrails/summary{args}"):
from flask import request
@@ -341,8 +341,8 @@ class TestSummaryAgentScoping:
return GuardrailSummary().get()
def _seed(self, pg_conn):
from application.storage.db.repositories.agents import AgentsRepository
from application.storage.db.repositories.guardrail_events import (
from docsgpt.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.guardrail_events import (
GuardrailEventsRepository,
)
@@ -400,7 +400,7 @@ class TestConfigRejectionDoesNotLeakInternals:
"""
def test_the_static_message_is_not_derived_from_the_exception(self):
from application.api.user.agents.routes import (
from docsgpt.api.user.agents.routes import (
INVALID_CONFIG_MESSAGE,
normalize_agent_config,
)
@@ -415,7 +415,7 @@ class TestConfigRejectionDoesNotLeakInternals:
assert INVALID_CONFIG_MESSAGE not in detail
def test_internal_validator_text_still_names_the_field(self):
from application.api.user.agents.routes import normalize_agent_config
from docsgpt.api.user.agents.routes import normalize_agent_config
with pytest.raises(ValueError, match="timeout_ms"):
normalize_agent_config({"guardrails": {"timeout_ms": 5}})
+7 -7
View File
@@ -1,4 +1,4 @@
"""Gap-coverage tests for application.api.user.agents.routes.
"""Gap-coverage tests for docsgpt.api.user.agents.routes.
No bson/ObjectId imports. The routes internally use ObjectId (patched where
needed) and Mongo collections (mocked). Repository assertions use
@@ -37,35 +37,35 @@ class TestNormalizeWorkflowReferenceGaps:
pass
def test_handles_integer_zero(self):
from application.api.user.agents.routes import normalize_workflow_reference
from docsgpt.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
from docsgpt.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
from docsgpt.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
from docsgpt.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
from docsgpt.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
from docsgpt.api.user.agents.routes import normalize_workflow_reference
result = normalize_workflow_reference({"other_key": "value"})
assert result is None
+122 -122
View File
@@ -1,4 +1,4 @@
"""Happy-path tests for application/api/user/agents/routes.py.
"""Happy-path tests for docsgpt/api/user/agents/routes.py.
Exercises each endpoint with the ephemeral ``pg_conn`` fixture and real
repository classes so the route bodies run end-to-end.
@@ -23,9 +23,9 @@ def _patch_db(conn):
yield conn
with patch(
"application.api.user.agents.routes.db_session", _yield
"docsgpt.api.user.agents.routes.db_session", _yield
), patch(
"application.api.user.agents.routes.db_readonly", _yield
"docsgpt.api.user.agents.routes.db_readonly", _yield
):
yield
@@ -34,12 +34,12 @@ def _seed_agent(
pg_conn, user="u", *, name="A", status="published", with_source=True,
retriever="classic", **extra,
):
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.agents import AgentsRepository
repo = AgentsRepository(pg_conn)
kwargs = {"description": "d", "retriever": retriever, **extra}
if with_source:
# Sources have a UUID FK; seed one
from application.storage.db.repositories.sources import SourcesRepository
from docsgpt.storage.db.repositories.sources import SourcesRepository
src = SourcesRepository(pg_conn).create("src", user_id=user)
kwargs["source_id"] = str(src["id"])
return repo.create(user, name, status, **kwargs)
@@ -47,7 +47,7 @@ def _seed_agent(
class TestGetAgent:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import GetAgent
from docsgpt.api.user.agents.routes import GetAgent
with app.test_request_context("/api/get_agent?id=x"):
from flask import request
@@ -59,7 +59,7 @@ class TestGetAgent:
assert status == 401
def test_returns_400_missing_id(self, app):
from application.api.user.agents.routes import GetAgent
from docsgpt.api.user.agents.routes import GetAgent
with app.test_request_context("/api/get_agent"):
from flask import request
@@ -71,7 +71,7 @@ class TestGetAgent:
assert status == 400
def test_returns_404_when_missing(self, app, pg_conn):
from application.api.user.agents.routes import GetAgent
from docsgpt.api.user.agents.routes import GetAgent
with _patch_db(pg_conn), app.test_request_context(
"/api/get_agent?id=00000000-0000-0000-0000-000000000000"
@@ -85,7 +85,7 @@ class TestGetAgent:
assert status == 404
def test_returns_agent_by_id(self, app, pg_conn):
from application.api.user.agents.routes import GetAgent
from docsgpt.api.user.agents.routes import GetAgent
user = "u-getA"
agent = _seed_agent(pg_conn, user=user, name="Al")
@@ -102,7 +102,7 @@ class TestGetAgent:
class TestGetAgents:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import GetAgents
from docsgpt.api.user.agents.routes import GetAgents
with app.test_request_context("/api/get_agents"):
from flask import request
@@ -114,7 +114,7 @@ class TestGetAgents:
assert status == 401
def test_returns_list_for_user(self, app, pg_conn):
from application.api.user.agents.routes import GetAgents
from docsgpt.api.user.agents.routes import GetAgents
user = "u-list-agents"
_seed_agent(pg_conn, user=user, name="B1")
@@ -129,7 +129,7 @@ class TestGetAgents:
assert "B1" in names and "B2" in names
def test_db_error_returns_400(self, app):
from application.api.user.agents.routes import GetAgents
from docsgpt.api.user.agents.routes import GetAgents
@contextmanager
def _broken():
@@ -137,7 +137,7 @@ class TestGetAgents:
yield
with patch(
"application.api.user.agents.routes.db_session", _broken
"docsgpt.api.user.agents.routes.db_session", _broken
), app.test_request_context("/api/get_agents"):
from flask import request
request.decoded_token = {"sub": "u"}
@@ -147,7 +147,7 @@ class TestGetAgents:
class TestDeleteAgent:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import DeleteAgent
from docsgpt.api.user.agents.routes import DeleteAgent
with app.test_request_context(
"/api/delete_agent?id=x", method="DELETE"
@@ -158,7 +158,7 @@ class TestDeleteAgent:
assert response.status_code == 401
def test_returns_400_missing_id(self, app):
from application.api.user.agents.routes import DeleteAgent
from docsgpt.api.user.agents.routes import DeleteAgent
with app.test_request_context(
"/api/delete_agent", method="DELETE"
@@ -169,7 +169,7 @@ class TestDeleteAgent:
assert response.status_code == 400
def test_returns_404_missing_agent(self, app, pg_conn):
from application.api.user.agents.routes import DeleteAgent
from docsgpt.api.user.agents.routes import DeleteAgent
with _patch_db(pg_conn), app.test_request_context(
"/api/delete_agent?id=00000000-0000-0000-0000-000000000000",
@@ -181,8 +181,8 @@ class TestDeleteAgent:
assert response.status_code == 404
def test_deletes_agent(self, app, pg_conn):
from application.api.user.agents.routes import DeleteAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import DeleteAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-delagent"
agent = _seed_agent(pg_conn, user=user)
@@ -199,7 +199,7 @@ class TestDeleteAgent:
class TestPinnedAgents:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import PinnedAgents
from docsgpt.api.user.agents.routes import PinnedAgents
with app.test_request_context("/api/pinned_agents"):
from flask import request
@@ -208,7 +208,7 @@ class TestPinnedAgents:
assert response.status_code == 401
def test_returns_empty_list_for_new_user(self, app, pg_conn):
from application.api.user.agents.routes import PinnedAgents
from docsgpt.api.user.agents.routes import PinnedAgents
with _patch_db(pg_conn), app.test_request_context("/api/pinned_agents"):
from flask import request
@@ -220,7 +220,7 @@ class TestPinnedAgents:
class TestPinAgent:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import PinAgent
from docsgpt.api.user.agents.routes import PinAgent
with app.test_request_context("/api/pin_agent?id=x", method="POST"):
from flask import request
@@ -229,7 +229,7 @@ class TestPinAgent:
assert response.status_code == 401
def test_returns_400_missing_id(self, app):
from application.api.user.agents.routes import PinAgent
from docsgpt.api.user.agents.routes import PinAgent
with app.test_request_context("/api/pin_agent", method="POST"):
from flask import request
@@ -238,7 +238,7 @@ class TestPinAgent:
assert response.status_code == 400
def test_pins_agent(self, app, pg_conn):
from application.api.user.agents.routes import PinAgent
from docsgpt.api.user.agents.routes import PinAgent
user = "u-pin"
agent = _seed_agent(pg_conn, user=user)
@@ -254,7 +254,7 @@ class TestPinAgent:
class TestGetTemplateAgents:
def test_returns_empty_without_templates(self, app, pg_conn):
from application.api.user.agents.routes import GetTemplateAgents
from docsgpt.api.user.agents.routes import GetTemplateAgents
with _patch_db(pg_conn), app.test_request_context(
"/api/template_agents"
@@ -266,8 +266,8 @@ class TestGetTemplateAgents:
assert response.json == []
def test_returns_templates(self, app, pg_conn):
from application.api.user.agents.routes import GetTemplateAgents
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import GetTemplateAgents
from docsgpt.storage.db.repositories.agents import AgentsRepository
AgentsRepository(pg_conn).create(
"__system__", "Template One", "template",
@@ -286,7 +286,7 @@ class TestGetTemplateAgents:
class TestAdoptAgent:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import AdoptAgent
from docsgpt.api.user.agents.routes import AdoptAgent
with app.test_request_context(
"/api/adopt_agent?id=x", method="POST"
@@ -297,7 +297,7 @@ class TestAdoptAgent:
assert response.status_code == 401
def test_returns_400_missing_id(self, app):
from application.api.user.agents.routes import AdoptAgent
from docsgpt.api.user.agents.routes import AdoptAgent
with app.test_request_context(
"/api/adopt_agent", method="POST"
@@ -310,7 +310,7 @@ class TestAdoptAgent:
class TestRemoveSharedAgent:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import RemoveSharedAgent
from docsgpt.api.user.agents.routes import RemoveSharedAgent
with app.test_request_context(
"/api/remove_shared_agent?id=x", method="POST"
@@ -321,7 +321,7 @@ class TestRemoveSharedAgent:
assert response.status_code == 401
def test_returns_400_missing_id(self, app):
from application.api.user.agents.routes import RemoveSharedAgent
from docsgpt.api.user.agents.routes import RemoveSharedAgent
with app.test_request_context(
"/api/remove_shared_agent", method="POST"
@@ -332,9 +332,9 @@ class TestRemoveSharedAgent:
assert response.status_code == 400
def test_removes_shared_agent(self, app, pg_conn):
from application.api.user.agents.routes import RemoveSharedAgent
from application.storage.db.repositories.agents import AgentsRepository
from application.storage.db.repositories.users import UsersRepository
from docsgpt.api.user.agents.routes import RemoveSharedAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.users import UsersRepository
owner = "owner-user"
viewer = "u-rm-shared"
@@ -357,7 +357,7 @@ class TestRemoveSharedAgent:
class TestCreateAgent:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import CreateAgent
from docsgpt.api.user.agents.routes import CreateAgent
with app.test_request_context(
"/api/create_agent",
@@ -373,7 +373,7 @@ class TestCreateAgent:
assert status == 401
def test_returns_400_missing_required_draft(self, app):
from application.api.user.agents.routes import CreateAgent
from docsgpt.api.user.agents.routes import CreateAgent
with app.test_request_context(
"/api/create_agent",
@@ -386,8 +386,8 @@ class TestCreateAgent:
assert response.status_code == 400
def test_creates_draft_classic_agent(self, app, pg_conn):
from application.api.user.agents.routes import CreateAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import CreateAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-create-A"
@@ -417,7 +417,7 @@ class TestCreateAgent:
class TestUpdateAgent:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
with app.test_request_context(
"/api/update_agent/abc", method="PUT",
@@ -429,7 +429,7 @@ class TestUpdateAgent:
assert response.status_code == 401
def test_returns_404_agent_not_found(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
with _patch_db(pg_conn), app.test_request_context(
"/api/update_agent/00000000-0000-0000-0000-000000000000",
@@ -444,8 +444,8 @@ class TestUpdateAgent:
assert response.status_code == 404
def test_updates_simple_fields(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import UpdateAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-upd-simple"
agent = _seed_agent(pg_conn, user=user, name="orig")
@@ -466,8 +466,8 @@ class TestUpdateAgent:
assert got["name"] == "new name"
def test_ignores_client_supplied_image_path(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import UpdateAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-upd-image-path"
agent = _seed_agent(
@@ -488,7 +488,7 @@ class TestUpdateAgent:
assert got["image"] is None
def test_invalid_status_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-upd-status"
agent = _seed_agent(pg_conn, user=user)
@@ -503,7 +503,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_invalid_source_uuid_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-upd-src"
agent = _seed_agent(pg_conn, user=user)
@@ -521,7 +521,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_source_default_clears(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-src-default"
agent = _seed_agent(pg_conn, user=user)
@@ -539,7 +539,7 @@ class TestUpdateAgent:
assert response.status_code == 200
def test_invalid_sources_item_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-sources"
agent = _seed_agent(pg_conn, user=user)
@@ -583,7 +583,7 @@ class TestUpdateAgent:
"""
import logging
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = f"u-log-{field}"
agent = _seed_agent(pg_conn, user=user)
@@ -607,7 +607,7 @@ class TestUpdateAgent:
), f"no WARN naming field={field!r} and user={user!r}; got {warnings!r}"
def test_invalid_chunks_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-upd-chunks"
agent = _seed_agent(pg_conn, user=user)
@@ -625,7 +625,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_negative_chunks_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-neg-chunks"
agent = _seed_agent(pg_conn, user=user)
@@ -643,7 +643,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_tools_must_be_list(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-tools"
agent = _seed_agent(pg_conn, user=user)
@@ -661,7 +661,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_limited_token_mode_requires_limit(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-limit"
agent = _seed_agent(pg_conn, user=user)
@@ -679,7 +679,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_limited_request_mode_requires_limit(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-req-limit"
agent = _seed_agent(pg_conn, user=user)
@@ -697,7 +697,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_token_limit_without_mode_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-token-mode"
agent = _seed_agent(pg_conn, user=user)
@@ -715,7 +715,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_invalid_prompt_id_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-upd-prompt"
agent = _seed_agent(pg_conn, user=user)
@@ -733,7 +733,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_prompt_id_default_clears(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-pid-default"
agent = _seed_agent(pg_conn, user=user)
@@ -751,7 +751,7 @@ class TestUpdateAgent:
assert response.status_code == 200
def test_empty_name_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-empty-name"
agent = _seed_agent(pg_conn, user=user)
@@ -766,7 +766,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_publish_classic_missing_fields_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-publish-missing"
agent = _seed_agent(pg_conn, user=user, with_source=False)
@@ -789,9 +789,9 @@ class TestUpdateAgent:
``Missing or invalid required fields: Source``. The retriever
carries the runtime identity, so the gate now accepts it.
"""
from application.api.user.agents.routes import UpdateAgent
from application.storage.db.repositories.agents import AgentsRepository
from application.storage.db.repositories.prompts import PromptsRepository
from docsgpt.api.user.agents.routes import UpdateAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.prompts import PromptsRepository
user = "u-publish-default"
# Draft agent with retriever='classic', no source_id, prompt + chunks set.
@@ -832,9 +832,9 @@ class TestUpdateAgent:
):
"""If neither a source nor a retriever is configured, the gate
still trips — the agent has no way to retrieve anything."""
from application.api.user.agents.routes import UpdateAgent
from application.storage.db.repositories.agents import AgentsRepository
from application.storage.db.repositories.prompts import PromptsRepository
from docsgpt.api.user.agents.routes import UpdateAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.prompts import PromptsRepository
user = "u-publish-no-retriever"
agent = _seed_agent(
@@ -868,15 +868,15 @@ class TestUpdateAgent:
assert "Source or retriever" in response.json.get("message", "")
def test_publishing_generates_api_key(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import UpdateAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-publish-key"
agent = _seed_agent(
pg_conn, user=user, status="draft", retriever="classic",
)
# Seed a prompt so published path validates
from application.storage.db.repositories.prompts import PromptsRepository
from docsgpt.storage.db.repositories.prompts import PromptsRepository
prompt = PromptsRepository(pg_conn).create(user, "p", "c")
AgentsRepository(pg_conn).update(
str(agent["id"]), user,
@@ -901,7 +901,7 @@ class TestUpdateAgent:
assert "key" in response.json
def test_invalid_json_in_form_field_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-bad-json"
agent = _seed_agent(pg_conn, user=user)
@@ -922,7 +922,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_empty_update_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-empty-upd"
agent = _seed_agent(pg_conn, user=user)
@@ -937,7 +937,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_db_error_returns_500(self, app):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
@contextmanager
def _broken():
@@ -945,7 +945,7 @@ class TestUpdateAgent:
yield
with patch(
"application.api.user.agents.routes.db_session", _broken
"docsgpt.api.user.agents.routes.db_session", _broken
), app.test_request_context(
"/api/update_agent/abc", method="PUT",
json={"name": "n", "description": "d", "status": "draft"},
@@ -956,7 +956,7 @@ class TestUpdateAgent:
assert response.status_code == 500
def test_allow_system_prompt_override(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-override"
agent = _seed_agent(pg_conn, user=user)
@@ -974,7 +974,7 @@ class TestUpdateAgent:
assert response.status_code == 200
def test_folder_id_null_clears(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-folder-null"
agent = _seed_agent(pg_conn, user=user)
@@ -992,7 +992,7 @@ class TestUpdateAgent:
assert response.status_code == 200
def test_publish_workflow_without_workflow_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-wf-publish"
agent = _seed_agent(pg_conn, user=user, with_source=False)
@@ -1012,7 +1012,7 @@ class TestUpdateAgent:
assert response.status_code == 400
def test_invalid_json_schema_returns_400(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-bad-schema"
agent = _seed_agent(pg_conn, user=user)
@@ -1033,7 +1033,7 @@ class TestUpdateAgent:
assert response.status_code in (200, 400)
def test_json_schema_empty_becomes_none(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from docsgpt.api.user.agents.routes import UpdateAgent
user = "u-schema-empty"
agent = _seed_agent(pg_conn, user=user)
@@ -1061,7 +1061,7 @@ class TestUpdateAgent:
class TestCreateAgentMore:
def test_invalid_status_returns_400(self, app):
from application.api.user.agents.routes import CreateAgent
from docsgpt.api.user.agents.routes import CreateAgent
with app.test_request_context(
"/api/create_agent", method="POST",
@@ -1076,7 +1076,7 @@ class TestCreateAgentMore:
assert status == 400
def test_publish_classic_without_source_returns_400(self, app):
from application.api.user.agents.routes import CreateAgent
from docsgpt.api.user.agents.routes import CreateAgent
with app.test_request_context(
"/api/create_agent", method="POST",
@@ -1096,7 +1096,7 @@ class TestCreateAgentMore:
assert status == 400
def test_unknown_agent_type_falls_back_classic(self, app, pg_conn):
from application.api.user.agents.routes import CreateAgent
from docsgpt.api.user.agents.routes import CreateAgent
user = "u-unknown-type"
with _patch_db(pg_conn), app.test_request_context(
@@ -1117,7 +1117,7 @@ class TestCreateAgentMore:
assert status == 201
def test_create_form_invalid_json_fields(self, app, pg_conn):
from application.api.user.agents.routes import CreateAgent
from docsgpt.api.user.agents.routes import CreateAgent
# Invalid JSON strings in form fields are coerced to []/None (no 400)
user = "u-form-json"
@@ -1145,7 +1145,7 @@ class TestCreateAgentMore:
def test_create_published_workflow_without_workflow_returns_400(
self, app, pg_conn,
):
from application.api.user.agents.routes import CreateAgent
from docsgpt.api.user.agents.routes import CreateAgent
user = "u-wf-no-wf"
with _patch_db(pg_conn), app.test_request_context(
@@ -1166,8 +1166,8 @@ class TestCreateAgentMore:
assert status == 400
def test_create_with_sources_list(self, app, pg_conn):
from application.api.user.agents.routes import CreateAgent
from application.storage.db.repositories.sources import (
from docsgpt.api.user.agents.routes import CreateAgent
from docsgpt.storage.db.repositories.sources import (
SourcesRepository,
)
@@ -1194,7 +1194,7 @@ class TestCreateAgentMore:
assert status == 201
def test_db_error_returns_400(self, app):
from application.api.user.agents.routes import CreateAgent
from docsgpt.api.user.agents.routes import CreateAgent
@contextmanager
def _broken():
@@ -1202,7 +1202,7 @@ class TestCreateAgentMore:
yield
with patch(
"application.api.user.agents.routes.db_session", _broken
"docsgpt.api.user.agents.routes.db_session", _broken
), app.test_request_context(
"/api/create_agent", method="POST",
json={
@@ -1226,8 +1226,8 @@ class TestCreateAgentMore:
class TestAdoptAgentMore:
def test_adopts_template_agent(self, app, pg_conn):
from application.api.user.agents.routes import AdoptAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import AdoptAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
repo = AgentsRepository(pg_conn)
template = repo.create("__system__", "Template X", "template")
@@ -1247,10 +1247,10 @@ class TestAdoptAgentMore:
import io
from unittest.mock import MagicMock
from application.api.user.agents.routes import AdoptAgent
from application.core.settings import settings
from application.storage.db.repositories.agents import AgentsRepository
from application.utils import (
from docsgpt.api.user.agents.routes import AdoptAgent
from docsgpt.core.settings import settings
from docsgpt.storage.db.repositories.agents import AgentsRepository
from docsgpt.utils import (
is_safe_agent_image_path,
safe_user_storage_component,
)
@@ -1268,7 +1268,7 @@ class TestAdoptAgentMore:
storage.save_file.side_effect = lambda data, path, **kw: {"path": path}
with _patch_db(pg_conn), patch(
"application.api.user.agents.routes.storage", storage
"docsgpt.api.user.agents.routes.storage", storage
), app.test_request_context(
f"/api/adopt_agent?id={template['id']}", method="POST"
):
@@ -1292,8 +1292,8 @@ class TestAdoptAgentMore:
def test_adopt_drops_image_when_copy_fails(self, app, pg_conn):
from unittest.mock import MagicMock
from application.api.user.agents.routes import AdoptAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import AdoptAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
repo = AgentsRepository(pg_conn)
template = repo.create(
@@ -1307,7 +1307,7 @@ class TestAdoptAgentMore:
storage.get_file.side_effect = FileNotFoundError("missing")
with _patch_db(pg_conn), patch(
"application.api.user.agents.routes.storage", storage
"docsgpt.api.user.agents.routes.storage", storage
), app.test_request_context(
f"/api/adopt_agent?id={template['id']}", method="POST"
):
@@ -1326,8 +1326,8 @@ class TestAdoptAgentMore:
def test_adopt_keeps_external_image_url(self, app, pg_conn):
from unittest.mock import MagicMock
from application.api.user.agents.routes import AdoptAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import AdoptAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
repo = AgentsRepository(pg_conn)
template = repo.create(
@@ -1340,7 +1340,7 @@ class TestAdoptAgentMore:
storage = MagicMock()
with _patch_db(pg_conn), patch(
"application.api.user.agents.routes.storage", storage
"docsgpt.api.user.agents.routes.storage", storage
), app.test_request_context(
f"/api/adopt_agent?id={template['id']}", method="POST"
):
@@ -1358,7 +1358,7 @@ class TestAdoptAgentMore:
storage.get_file.assert_not_called()
def test_adopt_template_missing_returns_404(self, app, pg_conn):
from application.api.user.agents.routes import AdoptAgent
from docsgpt.api.user.agents.routes import AdoptAgent
with _patch_db(pg_conn), app.test_request_context(
"/api/adopt_agent?id=00000000-0000-0000-0000-000000000000",
@@ -1370,7 +1370,7 @@ class TestAdoptAgentMore:
assert response.status_code == 404
def test_db_error_returns_400(self, app):
from application.api.user.agents.routes import AdoptAgent
from docsgpt.api.user.agents.routes import AdoptAgent
@contextmanager
def _broken():
@@ -1378,7 +1378,7 @@ class TestAdoptAgentMore:
yield
with patch(
"application.api.user.agents.routes.db_session", _broken
"docsgpt.api.user.agents.routes.db_session", _broken
), app.test_request_context(
"/api/adopt_agent?id=abc", method="POST"
):
@@ -1391,8 +1391,8 @@ class TestAdoptAgentMore:
class TestPinAgentMore:
def test_toggle_pin(self, app, pg_conn):
"""PinAgent is a toggle — second call unpins."""
from application.api.user.agents.routes import PinAgent
from application.storage.db.repositories.users import UsersRepository
from docsgpt.api.user.agents.routes import PinAgent
from docsgpt.storage.db.repositories.users import UsersRepository
user = "u-pin-toggle"
agent = _seed_agent(pg_conn, user=user)
@@ -1420,7 +1420,7 @@ class TestPinAgentMore:
assert response.status_code == 200
def test_pin_db_error_returns_500(self, app):
from application.api.user.agents.routes import PinAgent
from docsgpt.api.user.agents.routes import PinAgent
@contextmanager
def _broken():
@@ -1428,7 +1428,7 @@ class TestPinAgentMore:
yield
with patch(
"application.api.user.agents.routes.db_session", _broken
"docsgpt.api.user.agents.routes.db_session", _broken
), app.test_request_context(
"/api/pin_agent?id=abc", method="POST"
):
@@ -1440,14 +1440,14 @@ class TestPinAgentMore:
class TestRegenerateAgentKey:
def _seed_published_with_key(self, pg_conn, user, key):
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.agents import AgentsRepository
agent = _seed_agent(pg_conn, user=user, status="published")
AgentsRepository(pg_conn).update(str(agent["id"]), user, {"key": key})
return agent
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.routes import RegenerateAgentKey
from docsgpt.api.user.agents.routes import RegenerateAgentKey
with app.test_request_context(
"/api/regenerate_agent_key/abc", method="POST"
@@ -1461,7 +1461,7 @@ class TestRegenerateAgentKey:
assert status == 401
def test_returns_404_when_missing(self, app, pg_conn):
from application.api.user.agents.routes import RegenerateAgentKey
from docsgpt.api.user.agents.routes import RegenerateAgentKey
with _patch_db(pg_conn), app.test_request_context(
"/api/regenerate_agent_key/00000000-0000-0000-0000-000000000000",
@@ -1476,7 +1476,7 @@ class TestRegenerateAgentKey:
def test_returns_404_for_non_owner(self, app, pg_conn):
"""Owner-only: another user cannot rotate someone else's key."""
from application.api.user.agents.routes import RegenerateAgentKey
from docsgpt.api.user.agents.routes import RegenerateAgentKey
owner = "u-owner-key"
agent = self._seed_published_with_key(pg_conn, owner, "owner-old-key")
@@ -1490,7 +1490,7 @@ class TestRegenerateAgentKey:
assert response.status_code == 404
def test_returns_400_for_draft_without_key(self, app, pg_conn):
from application.api.user.agents.routes import RegenerateAgentKey
from docsgpt.api.user.agents.routes import RegenerateAgentKey
user = "u-draft-key"
agent = _seed_agent(pg_conn, user=user, status="draft")
@@ -1504,8 +1504,8 @@ class TestRegenerateAgentKey:
assert response.status_code == 400
def test_regenerates_key_and_invalidates_old(self, app, pg_conn):
from application.api.user.agents.routes import RegenerateAgentKey
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import RegenerateAgentKey
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-regen"
old_key = "regen-old-key"
@@ -1536,17 +1536,17 @@ class TestRegenerateAgentKey:
not owner-scoped, and the conversation is created with api_key set but
agent_id NULL to prove the api_key-only path is covered.
"""
from application.api.user.agents.routes import RegenerateAgentKey
from application.storage.db.repositories.conversations import (
from docsgpt.api.user.agents.routes import RegenerateAgentKey
from docsgpt.storage.db.repositories.conversations import (
ConversationsRepository,
)
from application.storage.db.repositories.shared_conversations import (
from docsgpt.storage.db.repositories.shared_conversations import (
SharedConversationsRepository,
)
from application.storage.db.repositories.stack_logs import (
from docsgpt.storage.db.repositories.stack_logs import (
StackLogsRepository,
)
from application.storage.db.repositories.token_usage import (
from docsgpt.storage.db.repositories.token_usage import (
TokenUsageRepository,
)
@@ -1601,7 +1601,7 @@ class TestRegenerateAgentKey:
assert _count("shared_conversations", new_key) == 1
def test_db_error_returns_500(self, app):
from application.api.user.agents.routes import RegenerateAgentKey
from docsgpt.api.user.agents.routes import RegenerateAgentKey
@contextmanager
def _broken():
@@ -1609,7 +1609,7 @@ class TestRegenerateAgentKey:
yield
with patch(
"application.api.user.agents.routes.db_session", _broken
"docsgpt.api.user.agents.routes.db_session", _broken
), app.test_request_context(
"/api/regenerate_agent_key/abc", method="POST"
):
@@ -1621,7 +1621,7 @@ class TestRegenerateAgentKey:
class TestPinnedAgentsListing:
def test_returns_pinned_after_pinning(self, app, pg_conn):
from application.api.user.agents.routes import PinAgent, PinnedAgents
from docsgpt.api.user.agents.routes import PinAgent, PinnedAgents
user = "u-pinned-list"
agent = _seed_agent(pg_conn, user=user, retriever="classic")
@@ -1646,7 +1646,7 @@ class TestPinnedAgentsListing:
assert response.json[0]["pinned"] is True
def test_pinned_db_error_returns_400(self, app):
from application.api.user.agents.routes import PinnedAgents
from docsgpt.api.user.agents.routes import PinnedAgents
@contextmanager
def _broken():
@@ -1654,7 +1654,7 @@ class TestPinnedAgentsListing:
yield
with patch(
"application.api.user.agents.routes.db_session", _broken
"docsgpt.api.user.agents.routes.db_session", _broken
), app.test_request_context("/api/pinned_agents"):
from flask import request
request.decoded_token = {"sub": "u"}
+30 -30
View File
@@ -1,4 +1,4 @@
"""Tests for helper functions in application/api/user/agents/routes.py."""
"""Tests for helper functions in docsgpt/api/user/agents/routes.py."""
import json
from contextlib import contextmanager
@@ -20,28 +20,28 @@ def _patch_db(conn):
yield conn
with patch(
"application.api.user.agents.routes.db_session", _yield
"docsgpt.api.user.agents.routes.db_session", _yield
), patch(
"application.api.user.agents.routes.db_readonly", _yield
"docsgpt.api.user.agents.routes.db_readonly", _yield
):
yield
class TestNormalizeWorkflowReference:
def test_none_returns_none(self):
from application.api.user.agents.routes import (
from docsgpt.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 (
from docsgpt.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 (
from docsgpt.api.user.agents.routes import (
normalize_workflow_reference,
)
assert (
@@ -49,25 +49,25 @@ class TestNormalizeWorkflowReference:
)
def test_dict_with_underscore_id(self):
from application.api.user.agents.routes import (
from docsgpt.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 (
from docsgpt.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 (
from docsgpt.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 (
from docsgpt.api.user.agents.routes import (
normalize_workflow_reference,
)
assert (
@@ -75,19 +75,19 @@ class TestNormalizeWorkflowReference:
)
def test_json_string_as_string(self):
from application.api.user.agents.routes import (
from docsgpt.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 (
from docsgpt.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 (
from docsgpt.api.user.agents.routes import (
normalize_workflow_reference,
)
assert normalize_workflow_reference(42) == "42"
@@ -95,14 +95,14 @@ class TestNormalizeWorkflowReference:
class TestResolveWorkflowForUser:
def test_none_workflow_returns_none(self, pg_conn):
from application.api.user.agents.routes import (
from docsgpt.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 (
from docsgpt.api.user.agents.routes import (
_resolve_workflow_for_user,
)
with app.app_context():
@@ -116,10 +116,10 @@ class TestResolveWorkflowForUser:
assert err.status_code == 404
def test_resolves_owned_workflow(self, pg_conn, app):
from application.api.user.agents.routes import (
from docsgpt.api.user.agents.routes import (
_resolve_workflow_for_user,
)
from application.storage.db.repositories.workflows import (
from docsgpt.storage.db.repositories.workflows import (
WorkflowsRepository,
)
@@ -135,13 +135,13 @@ class TestResolveWorkflowForUser:
class TestResolveFolderId:
def test_none_returns_none(self, pg_conn):
from application.api.user.agents.routes import _resolve_folder_id
from docsgpt.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
from docsgpt.api.user.agents.routes import _resolve_folder_id
with app.app_context():
pg_id, err = _resolve_folder_id(
@@ -151,8 +151,8 @@ class TestResolveFolderId:
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 (
from docsgpt.api.user.agents.routes import _resolve_folder_id
from docsgpt.storage.db.repositories.agent_folders import (
AgentFoldersRepository,
)
@@ -166,7 +166,7 @@ class TestResolveFolderId:
class TestFormatAgentOutput:
def test_basic_shape(self):
from application.api.user.agents.routes import _format_agent_output
from docsgpt.api.user.agents.routes import _format_agent_output
agent = {
"id": "agent-1",
@@ -191,21 +191,21 @@ class TestFormatAgentOutput:
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
from docsgpt.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
from docsgpt.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
from docsgpt.api.user.agents.routes import _format_agent_output
agent = {
"id": "a", "name": "n",
@@ -218,7 +218,7 @@ class TestFormatAgentOutput:
class TestBuildCreateKwargs:
def test_classic_kwargs(self):
from application.api.user.agents.routes import _build_create_kwargs
from docsgpt.api.user.agents.routes import _build_create_kwargs
data = {
"description": "d",
@@ -233,7 +233,7 @@ class TestBuildCreateKwargs:
assert out["chunks"] == 3
def test_invalid_chunks_skipped(self, app):
from application.api.user.agents.routes import _build_create_kwargs
from docsgpt.api.user.agents.routes import _build_create_kwargs
data = {"chunks": "abc"}
with app.app_context():
@@ -243,7 +243,7 @@ class TestBuildCreateKwargs:
assert "chunks" not in out
def test_prompt_id_default_not_set(self):
from application.api.user.agents.routes import _build_create_kwargs
from docsgpt.api.user.agents.routes import _build_create_kwargs
data = {"prompt_id": "default"}
out = _build_create_kwargs(
@@ -252,7 +252,7 @@ class TestBuildCreateKwargs:
assert "prompt_id" not in out
def test_image_url_used_when_provided(self):
from application.api.user.agents.routes import _build_create_kwargs
from docsgpt.api.user.agents.routes import _build_create_kwargs
out = _build_create_kwargs(
{}, image_url="/upload/img.png", agent_type="classic",
@@ -260,7 +260,7 @@ class TestBuildCreateKwargs:
assert out.get("image") == "/upload/img.png"
def test_client_image_path_is_never_persisted(self):
from application.api.user.agents.routes import _build_create_kwargs
from docsgpt.api.user.agents.routes import _build_create_kwargs
out = _build_create_kwargs(
{"image": ".env"}, image_url="", agent_type="classic",
+25 -25
View File
@@ -1,4 +1,4 @@
"""Tests for application/api/user/agents/sharing.py.
"""Tests for docsgpt/api/user/agents/sharing.py.
Uses the ephemeral ``pg_conn`` fixture to exercise the real PG repository
code paths (agents, users).
@@ -23,15 +23,15 @@ def _patch_db(conn):
yield conn
with patch(
"application.api.user.agents.sharing.db_session", _yield
"docsgpt.api.user.agents.sharing.db_session", _yield
), patch(
"application.api.user.agents.sharing.db_readonly", _yield
"docsgpt.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
from docsgpt.storage.db.repositories.agents import AgentsRepository
agent = AgentsRepository(pg_conn).create(
user_id,
"Agent",
@@ -49,7 +49,7 @@ def _make_agent(pg_conn, user_id="owner", *, shared=False, shared_token=None):
class TestSharedAgentGet:
def test_returns_400_missing_token(self, app):
from application.api.user.agents.sharing import SharedAgent
from docsgpt.api.user.agents.sharing import SharedAgent
with app.test_request_context("/api/shared_agent"):
from flask import request
@@ -58,7 +58,7 @@ class TestSharedAgentGet:
assert response.status_code == 400
def test_returns_404_for_unknown_token(self, app, pg_conn):
from application.api.user.agents.sharing import SharedAgent
from docsgpt.api.user.agents.sharing import SharedAgent
with _patch_db(pg_conn), app.test_request_context(
"/api/shared_agent?token=unknown"
@@ -69,7 +69,7 @@ class TestSharedAgentGet:
assert response.status_code == 404
def test_returns_agent_for_known_token(self, app, pg_conn):
from application.api.user.agents.sharing import SharedAgent
from docsgpt.api.user.agents.sharing import SharedAgent
_make_agent(pg_conn, shared=True, shared_token="abc123")
@@ -85,7 +85,7 @@ class TestSharedAgentGet:
assert data["shared"] is True
def test_records_shared_with_different_user(self, app, pg_conn):
from application.api.user.agents.sharing import SharedAgent
from docsgpt.api.user.agents.sharing import SharedAgent
_make_agent(pg_conn, user_id="owner", shared=True, shared_token="tk1")
@@ -98,7 +98,7 @@ class TestSharedAgentGet:
assert response.status_code == 200
def test_db_error_returns_400(self, app):
from application.api.user.agents.sharing import SharedAgent
from docsgpt.api.user.agents.sharing import SharedAgent
@contextmanager
def _broken():
@@ -106,7 +106,7 @@ class TestSharedAgentGet:
yield
with patch(
"application.api.user.agents.sharing.db_readonly", _broken
"docsgpt.api.user.agents.sharing.db_readonly", _broken
), app.test_request_context("/api/shared_agent?token=x"):
from flask import request
request.decoded_token = None
@@ -116,7 +116,7 @@ class TestSharedAgentGet:
class TestSharedAgents:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.sharing import SharedAgents
from docsgpt.api.user.agents.sharing import SharedAgents
with app.test_request_context("/api/shared_agents"):
from flask import request
@@ -125,7 +125,7 @@ class TestSharedAgents:
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
from docsgpt.api.user.agents.sharing import SharedAgents
with _patch_db(pg_conn), app.test_request_context(
"/api/shared_agents"
@@ -139,7 +139,7 @@ class TestSharedAgents:
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
from docsgpt.api.user.agents.sharing import SharedAgent, SharedAgents
_make_agent(pg_conn, user_id="owner", shared=True, shared_token="tk2")
@@ -162,7 +162,7 @@ class TestSharedAgents:
assert response.json[0]["shared"] is True
def test_db_error_returns_400(self, app):
from application.api.user.agents.sharing import SharedAgents
from docsgpt.api.user.agents.sharing import SharedAgents
@contextmanager
def _broken():
@@ -170,7 +170,7 @@ class TestSharedAgents:
yield
with patch(
"application.api.user.agents.sharing.db_session", _broken
"docsgpt.api.user.agents.sharing.db_session", _broken
), app.test_request_context("/api/shared_agents"):
from flask import request
request.decoded_token = {"sub": "u"}
@@ -180,7 +180,7 @@ class TestSharedAgents:
class TestShareAgent:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.sharing import ShareAgent
from docsgpt.api.user.agents.sharing import ShareAgent
with app.test_request_context(
"/api/share_agent", method="PUT", json={"id": "x", "shared": True}
@@ -191,7 +191,7 @@ class TestShareAgent:
assert response.status_code == 401
def test_returns_400_missing_id(self, app):
from application.api.user.agents.sharing import ShareAgent
from docsgpt.api.user.agents.sharing import ShareAgent
with app.test_request_context(
"/api/share_agent", method="PUT", json={"shared": True}
@@ -202,7 +202,7 @@ class TestShareAgent:
assert response.status_code == 400
def test_returns_400_missing_shared(self, app):
from application.api.user.agents.sharing import ShareAgent
from docsgpt.api.user.agents.sharing import ShareAgent
with app.test_request_context(
"/api/share_agent", method="PUT", json={"id": "x"}
@@ -213,7 +213,7 @@ class TestShareAgent:
assert response.status_code == 400
def test_returns_404_agent_not_found(self, app, pg_conn):
from application.api.user.agents.sharing import ShareAgent
from docsgpt.api.user.agents.sharing import ShareAgent
with _patch_db(pg_conn), app.test_request_context(
"/api/share_agent",
@@ -226,8 +226,8 @@ class TestShareAgent:
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
from docsgpt.api.user.agents.sharing import ShareAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
agent = _make_agent(pg_conn, user_id="owner")
agent_id = str(agent["id"])
@@ -250,8 +250,8 @@ class TestShareAgent:
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
from docsgpt.api.user.agents.sharing import ShareAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
agent = _make_agent(pg_conn, user_id="owner", shared=True, shared_token="tk")
agent_id = str(agent["id"])
@@ -270,7 +270,7 @@ class TestShareAgent:
assert got["shared_token"] is None
def test_db_error_returns_400(self, app):
from application.api.user.agents.sharing import ShareAgent
from docsgpt.api.user.agents.sharing import ShareAgent
@contextmanager
def _broken():
@@ -278,7 +278,7 @@ class TestShareAgent:
yield
with patch(
"application.api.user.agents.sharing.db_session", _broken
"docsgpt.api.user.agents.sharing.db_session", _broken
), app.test_request_context(
"/api/share_agent",
method="PUT",
+28 -28
View File
@@ -1,4 +1,4 @@
"""Gap-coverage tests for application.api.user.agents.webhooks.
"""Gap-coverage tests for docsgpt.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
@@ -47,13 +47,13 @@ class TestAgentWebhookListenerGaps:
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
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
mock_task = Mock()
mock_task.id = "task_empty"
with patch(
"application.api.user.agents.webhooks.process_agent_webhook"
"docsgpt.api.user.agents.webhooks.process_agent_webhook"
) as mock_process:
mock_process.apply_async.return_value = mock_task
with app.test_request_context(
@@ -69,13 +69,13 @@ class TestAgentWebhookListenerGaps:
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
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
mock_task = Mock()
mock_task.id = "task_noqs"
with patch(
"application.api.user.agents.webhooks.process_agent_webhook"
"docsgpt.api.user.agents.webhooks.process_agent_webhook"
) as mock_process:
mock_process.apply_async.return_value = mock_task
with app.test_request_context(
@@ -96,13 +96,13 @@ class TestAgentWebhookListenerGaps:
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
from docsgpt.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"
"docsgpt.api.user.agents.webhooks.process_agent_webhook"
) as mock_process:
mock_process.apply_async.return_value = mock_task
with app.test_request_context(
@@ -118,10 +118,10 @@ class TestAgentWebhookListenerGaps:
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
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
with patch(
"application.api.user.agents.webhooks.process_agent_webhook"
"docsgpt.api.user.agents.webhooks.process_agent_webhook"
) as mock_process:
mock_process.apply_async.side_effect = RuntimeError("celery is down")
with app.test_request_context(
@@ -149,9 +149,9 @@ def _patch_webhooks_db(conn):
yield conn
with patch(
"application.api.user.agents.webhooks.db_session", _yield
"docsgpt.api.user.agents.webhooks.db_session", _yield
), patch(
"application.api.user.agents.webhooks.db_readonly", _yield
"docsgpt.api.user.agents.webhooks.db_readonly", _yield
):
yield
@@ -163,16 +163,16 @@ def _patch_base_db(conn):
yield conn
with patch(
"application.api.user.base.db_readonly", _yield
"docsgpt.api.user.base.db_readonly", _yield
), patch(
"application.api.user.base.db_session", _yield
"docsgpt.api.user.base.db_session", _yield
):
yield
class TestAgentWebhookGet:
def test_returns_401_unauthenticated(self, app):
from application.api.user.agents.webhooks import AgentWebhook
from docsgpt.api.user.agents.webhooks import AgentWebhook
with app.test_request_context("/api/agent_webhook?id=x"):
from flask import request
@@ -181,7 +181,7 @@ class TestAgentWebhookGet:
assert response.status_code == 401
def test_returns_400_missing_id(self, app):
from application.api.user.agents.webhooks import AgentWebhook
from docsgpt.api.user.agents.webhooks import AgentWebhook
with app.test_request_context("/api/agent_webhook"):
from flask import request
@@ -190,7 +190,7 @@ class TestAgentWebhookGet:
assert response.status_code == 400
def test_returns_404_missing_agent(self, app, pg_conn):
from application.api.user.agents.webhooks import AgentWebhook
from docsgpt.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"
@@ -201,14 +201,14 @@ class TestAgentWebhookGet:
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
from docsgpt.api.user.agents.webhooks import AgentWebhook
from docsgpt.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",
"docsgpt.api.user.agents.webhooks.settings.API_URL",
"https://api.test",
), app.test_request_context(
f"/api/agent_webhook?id={agent['id']}"
@@ -222,8 +222,8 @@ class TestAgentWebhookGet:
)
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
from docsgpt.api.user.agents.webhooks import AgentWebhook
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-wh-reuse"
agent = AgentsRepository(pg_conn).create(
@@ -231,7 +231,7 @@ class TestAgentWebhookGet:
)
with _patch_webhooks_db(pg_conn), patch(
"application.api.user.agents.webhooks.settings.API_URL",
"docsgpt.api.user.agents.webhooks.settings.API_URL",
"https://api.test",
), app.test_request_context(
f"/api/agent_webhook?id={agent['id']}"
@@ -244,8 +244,8 @@ class TestAgentWebhookGet:
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 docsgpt.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.storage.db.repositories.agents import AgentsRepository
from unittest.mock import MagicMock
user = "u-wh-enq"
@@ -255,7 +255,7 @@ class TestAgentWebhookListener:
fake_task = MagicMock(id="task-post")
with patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
return_value=fake_task,
), app.test_request_context(
"/api/webhooks/agents/tk-enq", method="POST",
@@ -272,8 +272,8 @@ class TestAgentWebhookListener:
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 docsgpt.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.storage.db.repositories.agents import AgentsRepository
from unittest.mock import MagicMock
user = "u-wh-get"
@@ -283,7 +283,7 @@ class TestAgentWebhookListener:
fake_task = MagicMock(id="task-get")
with patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
return_value=fake_task,
), app.test_request_context(
"/api/webhooks/agents/tk-get?foo=bar&baz=42"
@@ -19,15 +19,15 @@ def _patch_db(conn):
yield conn
with patch(
"application.api.user.agents.webhooks.db_session", _yield
"docsgpt.api.user.agents.webhooks.db_session", _yield
), patch(
"application.api.user.agents.webhooks.db_readonly", _yield
"docsgpt.api.user.agents.webhooks.db_readonly", _yield
):
yield
def _seed_agent(pg_conn, user="u", token="tk", **kw):
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.agents import AgentsRepository
return AgentsRepository(pg_conn).create(
user, "a", "published", incoming_webhook_token=token, **kw,
)
@@ -42,13 +42,13 @@ def _apply_async_mock():
class TestWebhookIdempotency:
def test_no_header_enqueues_normally(self, app, pg_conn):
from application.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
agent = _seed_agent(pg_conn, user="u-noh", token="tk-noh")
apply_mock = _apply_async_mock()
with _patch_db(pg_conn), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
), app.test_request_context(
"/api/webhooks/agents/tk-noh", method="POST",
@@ -66,13 +66,13 @@ class TestWebhookIdempotency:
def test_header_first_post_records_row(self, app, pg_conn):
from sqlalchemy import text
from application.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
agent = _seed_agent(pg_conn, user="u-first", token="tk-first")
apply_mock = _apply_async_mock()
with _patch_db(pg_conn), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
), app.test_request_context(
"/api/webhooks/agents/tk-first", method="POST",
@@ -105,13 +105,13 @@ class TestWebhookIdempotency:
"""The Celery task body needs the key so ``with_idempotency`` can
record terminal status and ``_derive_source_id`` can pick it up.
"""
from application.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
agent = _seed_agent(pg_conn, user="u-fwd", token="tk-fwd")
apply_mock = _apply_async_mock()
with _patch_db(pg_conn), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
), app.test_request_context(
"/api/webhooks/agents/tk-fwd", method="POST",
@@ -133,13 +133,13 @@ class TestWebhookIdempotency:
)
def test_same_header_second_post_returns_cached(self, app, pg_conn):
from application.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
agent = _seed_agent(pg_conn, user="u-rep", token="tk-rep")
apply_mock = _apply_async_mock()
with _patch_db(pg_conn), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
):
with app.test_request_context(
@@ -179,8 +179,8 @@ class TestWebhookIdempotency:
from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager
from application.api.user.agents.webhooks import AgentWebhookListener
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.storage.db.repositories.agents import AgentsRepository
with pg_engine.begin() as conn:
agent = AgentsRepository(conn).create(
@@ -218,13 +218,13 @@ class TestWebhookIdempotency:
# module-attribute patches once before fanning out so every
# thread sees the mock instead of racing on save/restore.
with patch(
"application.api.user.agents.webhooks.db_session",
"docsgpt.api.user.agents.webhooks.db_session",
_engine_session,
), patch(
"application.api.user.agents.webhooks.db_readonly",
"docsgpt.api.user.agents.webhooks.db_readonly",
_engine_readonly,
), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
), ThreadPoolExecutor(max_workers=8) as ex:
responses = list(ex.map(fire, range(8)))
@@ -243,14 +243,14 @@ class TestWebhookIdempotency:
"""
from sqlalchemy import text as sql_text
from application.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
agent_a = _seed_agent(pg_conn, user="u-a", token="tk-a")
agent_b = _seed_agent(pg_conn, user="u-b", token="tk-b")
apply_mock = _apply_async_mock()
with _patch_db(pg_conn), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
):
with app.test_request_context(
@@ -296,13 +296,13 @@ class TestWebhookIdempotency:
def test_empty_header_treated_as_absent(self, app, pg_conn):
from sqlalchemy import text
from application.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
agent = _seed_agent(pg_conn, user="u-empty", token="tk-empty")
apply_mock = _apply_async_mock()
with _patch_db(pg_conn), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
), app.test_request_context(
"/api/webhooks/agents/tk-empty", method="POST",
@@ -323,13 +323,13 @@ class TestWebhookIdempotency:
assert count == 0
def test_oversized_header_rejected_with_400(self, app, pg_conn):
from application.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
agent = _seed_agent(pg_conn, user="u-big", token="tk-big")
oversized = "x" * 257
with _patch_db(pg_conn), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
) as mock_apply, app.test_request_context(
"/api/webhooks/agents/tk-big", method="POST",
json={"event": "x"},
@@ -351,14 +351,14 @@ class TestWebhookIdempotency:
"""
from sqlalchemy import text
from application.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
agent = _seed_agent(pg_conn, user="u-stale", token="tk-stale")
apply_mock = _apply_async_mock()
# First POST creates a dedup row.
with _patch_db(pg_conn), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
), app.test_request_context(
"/api/webhooks/agents/tk-stale", method="POST",
@@ -388,7 +388,7 @@ class TestWebhookIdempotency:
# Second POST with the same key must enqueue again, not silently dedup.
with _patch_db(pg_conn), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
), app.test_request_context(
"/api/webhooks/agents/tk-stale", method="POST",
+34 -34
View File
@@ -26,22 +26,22 @@ def _patch_db(conn):
yield conn
with patch(
"application.api.user.agents.routes.db_session", _yield
"docsgpt.api.user.agents.routes.db_session", _yield
), patch(
"application.api.user.agents.routes.db_readonly", _yield
"docsgpt.api.user.agents.routes.db_readonly", _yield
):
yield
def _seed_workflow(pg_conn, user, *, name="WF"):
"""Create a start → agent → end workflow; returns (workflow, nodes, edges)."""
from application.storage.db.repositories.workflow_edges import (
from docsgpt.storage.db.repositories.workflow_edges import (
WorkflowEdgesRepository,
)
from application.storage.db.repositories.workflow_nodes import (
from docsgpt.storage.db.repositories.workflow_nodes import (
WorkflowNodesRepository,
)
from application.storage.db.repositories.workflows import WorkflowsRepository
from docsgpt.storage.db.repositories.workflows import WorkflowsRepository
wf = WorkflowsRepository(pg_conn).create(user, name, description="d")
wf_id = str(wf["id"])
@@ -80,10 +80,10 @@ def _seed_workflow(pg_conn, user, *, name="WF"):
def _graph(pg_conn, workflow_id, version=1):
from application.storage.db.repositories.workflow_edges import (
from docsgpt.storage.db.repositories.workflow_edges import (
WorkflowEdgesRepository,
)
from application.storage.db.repositories.workflow_nodes import (
from docsgpt.storage.db.repositories.workflow_nodes import (
WorkflowNodesRepository,
)
@@ -94,7 +94,7 @@ def _graph(pg_conn, workflow_id, version=1):
class TestCloneToUser:
def test_clones_graph_for_new_owner(self, pg_conn):
from application.storage.db.repositories.workflows import WorkflowsRepository
from docsgpt.storage.db.repositories.workflows import WorkflowsRepository
wf, _, _ = _seed_workflow(pg_conn, "owner-a", name="Source WF")
clone = WorkflowsRepository(pg_conn).clone_to_user(
@@ -122,7 +122,7 @@ class TestCloneToUser:
assert len(src_nodes) == 3 and len(src_edges) == 2
def test_wrong_owner_returns_none(self, pg_conn):
from application.storage.db.repositories.workflows import WorkflowsRepository
from docsgpt.storage.db.repositories.workflows import WorkflowsRepository
wf, _, _ = _seed_workflow(pg_conn, "owner-a")
assert (
@@ -133,7 +133,7 @@ class TestCloneToUser:
)
def test_missing_workflow_returns_none(self, pg_conn):
from application.storage.db.repositories.workflows import WorkflowsRepository
from docsgpt.storage.db.repositories.workflows import WorkflowsRepository
assert (
WorkflowsRepository(pg_conn).clone_to_user(
@@ -145,9 +145,9 @@ class TestCloneToUser:
class TestAdoptWorkflowAgent:
def test_adopt_clones_the_graph(self, app, pg_conn):
from application.api.user.agents.routes import AdoptAgent
from application.storage.db.repositories.agents import AgentsRepository
from application.storage.db.repositories.workflows import WorkflowsRepository
from docsgpt.api.user.agents.routes import AdoptAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.workflows import WorkflowsRepository
wf, _, _ = _seed_workflow(pg_conn, "__system__", name="Template WF")
repo = AgentsRepository(pg_conn)
@@ -187,8 +187,8 @@ class TestAdoptWorkflowAgent:
Publishing the agent anyway would hand the adopter a workflow agent
with no graph — permanently unrunnable — so the adopt fails instead.
"""
from application.api.user.agents.routes import AdoptAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import AdoptAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
wf, _, _ = _seed_workflow(pg_conn, "someone-else")
repo = AgentsRepository(pg_conn)
@@ -219,16 +219,16 @@ class TestAdoptWorkflowAgent:
def test_adopt_strips_foreign_node_refs(self, app, pg_conn):
"""Cloned agent nodes keep builtin tool ids but shed the owner's refs."""
from application.agents.default_tools import default_tool_id
from application.api.user.agents.routes import AdoptAgent
from application.storage.db.repositories.agents import AgentsRepository
from application.storage.db.repositories.user_tools import (
from docsgpt.agents.default_tools import default_tool_id
from docsgpt.api.user.agents.routes import AdoptAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.user_tools import (
UserToolsRepository,
)
from application.storage.db.repositories.workflow_nodes import (
from docsgpt.storage.db.repositories.workflow_nodes import (
WorkflowNodesRepository,
)
from application.storage.db.repositories.workflows import WorkflowsRepository
from docsgpt.storage.db.repositories.workflows import WorkflowsRepository
owner_tool = UserToolsRepository(pg_conn).create(
"__system__", "brave", config={}, display_name="Brave"
@@ -291,9 +291,9 @@ class TestAdoptWorkflowAgent:
class TestDeleteWorkflowAgent:
def test_delete_removes_owned_workflow(self, app, pg_conn):
from application.api.user.agents.routes import DeleteAgent
from application.storage.db.repositories.agents import AgentsRepository
from application.storage.db.repositories.workflows import WorkflowsRepository
from docsgpt.api.user.agents.routes import DeleteAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.workflows import WorkflowsRepository
user = "u-wf-del"
wf, _, _ = _seed_workflow(pg_conn, user)
@@ -319,9 +319,9 @@ class TestDeleteWorkflowAgent:
"""Regression: deleting an agent whose ``workflow_id`` points at another
user's workflow (the pre-clone adopted shape) must not touch that graph.
The old explicit node/edge cleanup was not owner-scoped and gutted it."""
from application.api.user.agents.routes import DeleteAgent
from application.storage.db.repositories.agents import AgentsRepository
from application.storage.db.repositories.workflows import WorkflowsRepository
from docsgpt.api.user.agents.routes import DeleteAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
from docsgpt.storage.db.repositories.workflows import WorkflowsRepository
wf, _, _ = _seed_workflow(pg_conn, "__system__", name="Shared Template WF")
repo = AgentsRepository(pg_conn)
@@ -350,8 +350,8 @@ class TestDeleteWorkflowAgent:
class TestCreateWorkflowAgent:
def test_create_published_with_owned_workflow(self, app, pg_conn):
from application.api.user.agents.routes import CreateAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import CreateAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-wf-create"
wf, _, _ = _seed_workflow(pg_conn, user)
@@ -381,7 +381,7 @@ class TestCreateWorkflowAgent:
assert agent["status"] == "published"
def test_create_published_with_unowned_workflow_returns_404(self, app, pg_conn):
from application.api.user.agents.routes import CreateAgent
from docsgpt.api.user.agents.routes import CreateAgent
wf, _, _ = _seed_workflow(pg_conn, "someone-else")
@@ -405,8 +405,8 @@ class TestCreateWorkflowAgent:
class TestUpdateWorkflowAgent:
def test_update_sets_and_clears_workflow(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import UpdateAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-wf-update"
wf, _, _ = _seed_workflow(pg_conn, user)
@@ -440,8 +440,8 @@ class TestUpdateWorkflowAgent:
assert repo.get(agent_id, user)["workflow_id"] is None
def test_publish_without_workflow_is_rejected(self, app, pg_conn):
from application.api.user.agents.routes import UpdateAgent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.routes import UpdateAgent
from docsgpt.storage.db.repositories.agents import AgentsRepository
user = "u-wf-update2"
agent = AgentsRepository(pg_conn).create(