mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-19 08:14:40 +00:00
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:
1 parent
3e7f1f91b4
commit
574f96341e
984 files changed
+9847
-9682
No files matched your search
@@ -1,9 +1,9 @@
|
||||
"""Tests for application/mcp_server.py.
|
||||
"""Tests for docsgpt/mcp_server.py.
|
||||
|
||||
The server module exposes one FastMCP tool, ``search_docs``, that reads
|
||||
the caller's ``Authorization: Bearer <key>`` header via
|
||||
``get_http_headers()`` and delegates to
|
||||
``application.services.search_service.search``. These tests exercise
|
||||
``docsgpt.services.search_service.search``. These tests exercise
|
||||
the tool directly by patching ``get_http_headers`` and ``search``; the
|
||||
full HTTP-layer plumbing (mount, lifespan, session handshake) is
|
||||
covered by ``tests/test_asgi.py``.
|
||||
@@ -18,20 +18,20 @@ import pytest
|
||||
class TestSearchDocsTool:
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_bearer_raises_permission_error(self):
|
||||
from application.mcp_server import search_docs
|
||||
from docsgpt.mcp_server import search_docs
|
||||
|
||||
with patch(
|
||||
"application.mcp_server.get_http_headers", return_value={}
|
||||
"docsgpt.mcp_server.get_http_headers", return_value={}
|
||||
):
|
||||
with pytest.raises(PermissionError):
|
||||
await search_docs(query="hi")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_non_bearer_header_raises_permission_error(self):
|
||||
from application.mcp_server import search_docs
|
||||
from docsgpt.mcp_server import search_docs
|
||||
|
||||
with patch(
|
||||
"application.mcp_server.get_http_headers",
|
||||
"docsgpt.mcp_server.get_http_headers",
|
||||
return_value={"authorization": "Basic dXNlcjpwYXNz"},
|
||||
):
|
||||
with pytest.raises(PermissionError):
|
||||
@@ -39,10 +39,10 @@ class TestSearchDocsTool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_blank_bearer_token_raises_permission_error(self):
|
||||
from application.mcp_server import search_docs
|
||||
from docsgpt.mcp_server import search_docs
|
||||
|
||||
with patch(
|
||||
"application.mcp_server.get_http_headers",
|
||||
"docsgpt.mcp_server.get_http_headers",
|
||||
return_value={"authorization": "Bearer "},
|
||||
):
|
||||
with pytest.raises(PermissionError):
|
||||
@@ -50,16 +50,16 @@ class TestSearchDocsTool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_api_key_raises_permission_error(self):
|
||||
from application.mcp_server import search_docs
|
||||
from application.services.search_service import InvalidAPIKey
|
||||
from docsgpt.mcp_server import search_docs
|
||||
from docsgpt.services.search_service import InvalidAPIKey
|
||||
|
||||
with (
|
||||
patch(
|
||||
"application.mcp_server.get_http_headers",
|
||||
"docsgpt.mcp_server.get_http_headers",
|
||||
return_value={"authorization": "Bearer bogus"},
|
||||
),
|
||||
patch(
|
||||
"application.mcp_server.search", side_effect=InvalidAPIKey()
|
||||
"docsgpt.mcp_server.search", side_effect=InvalidAPIKey()
|
||||
),
|
||||
):
|
||||
with pytest.raises(PermissionError):
|
||||
@@ -67,16 +67,16 @@ class TestSearchDocsTool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_failed_bubbles_up(self):
|
||||
from application.mcp_server import search_docs
|
||||
from application.services.search_service import SearchFailed
|
||||
from docsgpt.mcp_server import search_docs
|
||||
from docsgpt.services.search_service import SearchFailed
|
||||
|
||||
with (
|
||||
patch(
|
||||
"application.mcp_server.get_http_headers",
|
||||
"docsgpt.mcp_server.get_http_headers",
|
||||
return_value={"authorization": "Bearer k"},
|
||||
),
|
||||
patch(
|
||||
"application.mcp_server.search",
|
||||
"docsgpt.mcp_server.search",
|
||||
side_effect=SearchFailed("boom"),
|
||||
),
|
||||
):
|
||||
@@ -85,16 +85,16 @@ class TestSearchDocsTool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_happy_path_passes_args_and_returns_hits(self):
|
||||
from application.mcp_server import search_docs
|
||||
from docsgpt.mcp_server import search_docs
|
||||
|
||||
hits = [{"text": "t", "title": "T", "source": "s"}]
|
||||
with (
|
||||
patch(
|
||||
"application.mcp_server.get_http_headers",
|
||||
"docsgpt.mcp_server.get_http_headers",
|
||||
return_value={"authorization": "Bearer the-key"},
|
||||
),
|
||||
patch(
|
||||
"application.mcp_server.search", return_value=hits
|
||||
"docsgpt.mcp_server.search", return_value=hits
|
||||
) as mock_search,
|
||||
):
|
||||
out = await search_docs(query="q", chunks=7)
|
||||
@@ -103,15 +103,15 @@ class TestSearchDocsTool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_chunks_is_5(self):
|
||||
from application.mcp_server import search_docs
|
||||
from docsgpt.mcp_server import search_docs
|
||||
|
||||
with (
|
||||
patch(
|
||||
"application.mcp_server.get_http_headers",
|
||||
"docsgpt.mcp_server.get_http_headers",
|
||||
return_value={"authorization": "Bearer k"},
|
||||
),
|
||||
patch(
|
||||
"application.mcp_server.search", return_value=[]
|
||||
"docsgpt.mcp_server.search", return_value=[]
|
||||
) as mock_search,
|
||||
):
|
||||
await search_docs(query="q")
|
||||
@@ -119,15 +119,15 @@ class TestSearchDocsTool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bearer_scheme_case_insensitive(self):
|
||||
from application.mcp_server import search_docs
|
||||
from docsgpt.mcp_server import search_docs
|
||||
|
||||
with (
|
||||
patch(
|
||||
"application.mcp_server.get_http_headers",
|
||||
"docsgpt.mcp_server.get_http_headers",
|
||||
return_value={"authorization": "bearer lowercase-scheme"},
|
||||
),
|
||||
patch(
|
||||
"application.mcp_server.search", return_value=[]
|
||||
"docsgpt.mcp_server.search", return_value=[]
|
||||
) as mock_search,
|
||||
):
|
||||
await search_docs(query="q")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Unit tests for application/services/search_service.py.
|
||||
"""Unit tests for docsgpt/services/search_service.py.
|
||||
|
||||
Tests exercise the service function in isolation — AgentsRepository is
|
||||
stubbed via a patched ``db_readonly`` context manager, and
|
||||
@@ -11,7 +11,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from application.services.search_service import (
|
||||
from docsgpt.services.search_service import (
|
||||
InvalidAPIKey,
|
||||
SearchFailed,
|
||||
_authorized_source_ids,
|
||||
@@ -31,11 +31,11 @@ def _fake_db_readonly(agent_data):
|
||||
yield MagicMock()
|
||||
|
||||
with patch(
|
||||
"application.api.user.team_sharing.can_access", return_value=True
|
||||
"docsgpt.api.user.team_sharing.can_access", return_value=True
|
||||
), patch(
|
||||
"application.services.search_service.db_readonly", _yield_conn
|
||||
"docsgpt.services.search_service.db_readonly", _yield_conn
|
||||
), patch(
|
||||
"application.services.search_service.AgentsRepository",
|
||||
"docsgpt.services.search_service.AgentsRepository",
|
||||
return_value=agents_repo,
|
||||
):
|
||||
yield
|
||||
@@ -75,9 +75,9 @@ class TestSearchInvalidAPIKey:
|
||||
agents_repo.find_by_key.side_effect = RuntimeError("db down")
|
||||
|
||||
with patch(
|
||||
"application.services.search_service.db_readonly", _yield_conn
|
||||
"docsgpt.services.search_service.db_readonly", _yield_conn
|
||||
), patch(
|
||||
"application.services.search_service.AgentsRepository",
|
||||
"docsgpt.services.search_service.AgentsRepository",
|
||||
return_value=agents_repo,
|
||||
):
|
||||
with pytest.raises(SearchFailed):
|
||||
@@ -93,12 +93,12 @@ class TestSearchEmptyWhenNoSources:
|
||||
assert search("k", "q", 5) == []
|
||||
|
||||
def test_returns_empty_for_zero_chunks_without_db_lookup(self):
|
||||
with patch("application.services.search_service.db_readonly") as mock_db:
|
||||
with patch("docsgpt.services.search_service.db_readonly") as mock_db:
|
||||
assert search("k", "q", 0) == []
|
||||
mock_db.assert_not_called()
|
||||
|
||||
def test_returns_empty_for_negative_chunks_without_db_lookup(self):
|
||||
with patch("application.services.search_service.db_readonly") as mock_db:
|
||||
with patch("docsgpt.services.search_service.db_readonly") as mock_db:
|
||||
assert search("k", "q", -1) == []
|
||||
mock_db.assert_not_called()
|
||||
|
||||
@@ -115,7 +115,7 @@ class TestSearchResults:
|
||||
}
|
||||
]
|
||||
with _fake_db_readonly(agent), patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
return_value=fake_vs,
|
||||
):
|
||||
results = search("k", "q", 5)
|
||||
@@ -133,7 +133,7 @@ class TestSearchResults:
|
||||
fake_vs.search.return_value = [lc_doc]
|
||||
|
||||
with _fake_db_readonly(agent), patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
return_value=fake_vs,
|
||||
):
|
||||
results = search("k", "q", 5)
|
||||
@@ -151,7 +151,7 @@ class TestSearchResults:
|
||||
fake_vs.search.return_value = docs
|
||||
|
||||
with _fake_db_readonly(agent), patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
return_value=fake_vs,
|
||||
):
|
||||
results = search("k", "q", 3)
|
||||
@@ -169,7 +169,7 @@ class TestSearchResults:
|
||||
fake_vs.search.return_value = docs
|
||||
|
||||
with _fake_db_readonly(agent), patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
return_value=fake_vs,
|
||||
):
|
||||
results = search("k", "q", 5)
|
||||
@@ -194,7 +194,7 @@ class TestSearchResults:
|
||||
return healthy_vs
|
||||
|
||||
with _fake_db_readonly(agent), patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
side_effect=create_vs,
|
||||
):
|
||||
results = search("k", "q", 5)
|
||||
@@ -208,7 +208,7 @@ class TestSearchResults:
|
||||
{"text": "body", "metadata": {"filename": "document.pdf"}}
|
||||
]
|
||||
with _fake_db_readonly(agent), patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
return_value=fake_vs,
|
||||
):
|
||||
results = search("k", "q", 5)
|
||||
@@ -221,7 +221,7 @@ class TestSearchResults:
|
||||
{"text": "Content without any title metadata at all", "metadata": {}}
|
||||
]
|
||||
with _fake_db_readonly(agent), patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
return_value=fake_vs,
|
||||
):
|
||||
results = search("k", "q", 5)
|
||||
@@ -232,7 +232,7 @@ class TestSearchResults:
|
||||
# ``source_id=" "`` only — after strip() this leaves no real source.
|
||||
agent = {"extra_source_ids": [" ", ""], "source_id": None}
|
||||
with _fake_db_readonly(agent), patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore"
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore"
|
||||
) as mock_create:
|
||||
results = search("k", "q", 5)
|
||||
mock_create.assert_not_called()
|
||||
@@ -254,14 +254,14 @@ class TestSourceAuthorization:
|
||||
return {"id": "agent-1", "user_id": "owner", "extra_source_ids": [], **kw}
|
||||
|
||||
def test_readable_sources_pass_through(self, monkeypatch):
|
||||
import application.api.user.team_sharing as ts
|
||||
import docsgpt.api.user.team_sharing as ts
|
||||
|
||||
monkeypatch.setattr(ts, "can_access", lambda *a, **k: True)
|
||||
agent = self._agent(extra_source_ids=["s1", "s2"])
|
||||
assert _authorized_source_ids(None, agent, ["s1", "s2"]) == ["s1", "s2"]
|
||||
|
||||
def test_foreign_source_is_dropped(self, monkeypatch):
|
||||
import application.api.user.team_sharing as ts
|
||||
import docsgpt.api.user.team_sharing as ts
|
||||
|
||||
monkeypatch.setattr(ts, "can_access", lambda conn, k, sid, u: sid == "mine")
|
||||
agent = self._agent()
|
||||
@@ -269,7 +269,7 @@ class TestSourceAuthorization:
|
||||
|
||||
def test_team_shared_source_is_kept(self, monkeypatch):
|
||||
"""A grant is legitimate access; only unreadable ids are dropped."""
|
||||
import application.api.user.team_sharing as ts
|
||||
import docsgpt.api.user.team_sharing as ts
|
||||
|
||||
monkeypatch.setattr(ts, "can_access", lambda *a, **k: True)
|
||||
agent = self._agent(user_id="grantee")
|
||||
@@ -280,7 +280,7 @@ class TestSourceAuthorization:
|
||||
assert _authorized_source_ids(None, agent, ["s1"]) == []
|
||||
|
||||
def test_check_failure_fails_closed(self, monkeypatch):
|
||||
import application.api.user.team_sharing as ts
|
||||
import docsgpt.api.user.team_sharing as ts
|
||||
|
||||
def _boom(*a, **k):
|
||||
raise RuntimeError("db down")
|
||||
@@ -291,7 +291,7 @@ class TestSourceAuthorization:
|
||||
|
||||
def _serial_search_sources(query, source_ids, chunks):
|
||||
"""The pre-fan-out ``_search_sources``, kept as a parity oracle."""
|
||||
from application.services.search_service import VectorCreator, settings
|
||||
from docsgpt.services.search_service import VectorCreator, settings
|
||||
|
||||
if chunks <= 0 or not source_ids:
|
||||
return []
|
||||
@@ -366,11 +366,11 @@ def _no_embedder_store(docs=None):
|
||||
|
||||
def _run_search_sources(query, source_ids, chunks, stores, serial=False):
|
||||
"""Run ``_search_sources`` (or its serial oracle) against ``stores``."""
|
||||
from application.services import search_service
|
||||
from docsgpt.services import search_service
|
||||
|
||||
impl = _serial_search_sources if serial else search_service._search_sources
|
||||
with patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
side_effect=lambda _type, source_id, _key: stores[source_id],
|
||||
):
|
||||
return impl(query, source_ids, chunks)
|
||||
@@ -555,10 +555,10 @@ class TestSearchSourcesFanOut:
|
||||
raise RuntimeError("connection failed")
|
||||
return stores[source_id]
|
||||
|
||||
from application.services import search_service
|
||||
from docsgpt.services import search_service
|
||||
|
||||
with patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
side_effect=_create,
|
||||
):
|
||||
results = search_service._search_sources("q", ["a", "b", "c"], 6)
|
||||
@@ -596,10 +596,10 @@ class TestSearchSourcesFanOut:
|
||||
embedder = _make_embedder()
|
||||
stores = {"a": _make_store(embedder, [{"text": "hit a", "metadata": {}}])}
|
||||
|
||||
from application.services import search_service
|
||||
from docsgpt.services import search_service
|
||||
|
||||
with patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore",
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore",
|
||||
side_effect=lambda _t, source_id, _k: stores[source_id],
|
||||
):
|
||||
results = search_service._search_sources("q", [" ", "a", ""], 6)
|
||||
@@ -608,19 +608,19 @@ class TestSearchSourcesFanOut:
|
||||
assert stores["a"].search.call_args.kwargs["k"] == 4
|
||||
|
||||
def test_all_blank_source_ids_build_no_store(self):
|
||||
from application.services import search_service
|
||||
from docsgpt.services import search_service
|
||||
|
||||
with patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore"
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore"
|
||||
) as mock_create:
|
||||
assert search_service._search_sources("q", [" ", ""], 5) == []
|
||||
mock_create.assert_not_called()
|
||||
|
||||
def test_zero_chunks_short_circuits(self):
|
||||
from application.services import search_service
|
||||
from docsgpt.services import search_service
|
||||
|
||||
with patch(
|
||||
"application.services.search_service.VectorCreator.create_vectorstore"
|
||||
"docsgpt.services.search_service.VectorCreator.create_vectorstore"
|
||||
) as mock_create:
|
||||
assert search_service._search_sources("q", ["a"], 0) == []
|
||||
mock_create.assert_not_called()
|
||||
Reference in new issue
Block a user