mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-04 10:13:06 +00:00
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.
180 lines
7.0 KiB
Python
180 lines
7.0 KiB
Python
"""Integration tests for ``UsersRepository`` against a live Postgres.
|
|
|
|
These tests need:
|
|
|
|
* A running Postgres reachable via ``POSTGRES_URI``
|
|
* Alembic migration ``0001_initial`` applied
|
|
|
|
They are skipped automatically by the default ``pytest`` run because
|
|
``pytest.ini`` has ``--ignore=tests/integration`` in ``addopts``. They
|
|
are additionally marked ``@pytest.mark.integration`` so they can be
|
|
selected/excluded explicitly. Run them locally with::
|
|
|
|
.venv/bin/python -m pytest tests/integration/test_users_repository.py \\
|
|
--override-ini="addopts=" --no-cov
|
|
|
|
Covers every operation the legacy Mongo code performs on
|
|
``users_collection``: upsert + get + add/remove on the pinned and
|
|
shared_with_me JSONB arrays, plus explicit tenant-isolation checks
|
|
(a user's operations must never touch another user's data).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from docsgpt.storage.db.repositories.users import UsersRepository
|
|
|
|
|
|
@pytest.fixture
|
|
def repo(pg_clean_users):
|
|
return UsersRepository(pg_clean_users)
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestUpsert:
|
|
def test_upsert_creates_new_user_with_default_preferences(self, repo):
|
|
doc = repo.upsert("alice@example.com")
|
|
assert doc["user_id"] == "alice@example.com"
|
|
assert doc["agent_preferences"] == {"pinned": [], "shared_with_me": []}
|
|
assert "id" in doc
|
|
assert "_id" in doc # Mongo-compat alias
|
|
|
|
def test_upsert_is_idempotent(self, repo):
|
|
first = repo.upsert("alice@example.com")
|
|
second = repo.upsert("alice@example.com")
|
|
assert first["id"] == second["id"]
|
|
assert first["agent_preferences"] == second["agent_preferences"]
|
|
|
|
def test_upsert_preserves_existing_preferences(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.add_pinned("alice@example.com", "agent-1")
|
|
doc = repo.upsert("alice@example.com")
|
|
assert doc["agent_preferences"]["pinned"] == ["agent-1"]
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestGet:
|
|
def test_get_returns_none_for_missing_user(self, repo):
|
|
assert repo.get("nobody@example.com") is None
|
|
|
|
def test_get_returns_dict_for_existing_user(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
doc = repo.get("alice@example.com")
|
|
assert doc is not None
|
|
assert doc["user_id"] == "alice@example.com"
|
|
assert doc["agent_preferences"] == {"pinned": [], "shared_with_me": []}
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestAddPinned:
|
|
def test_add_pinned_to_new_user_creates_user(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.add_pinned("alice@example.com", "agent-1")
|
|
doc = repo.get("alice@example.com")
|
|
assert doc["agent_preferences"]["pinned"] == ["agent-1"]
|
|
|
|
def test_add_pinned_is_idempotent(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.add_pinned("alice@example.com", "agent-1")
|
|
repo.add_pinned("alice@example.com", "agent-1")
|
|
doc = repo.get("alice@example.com")
|
|
assert doc["agent_preferences"]["pinned"] == ["agent-1"]
|
|
|
|
def test_add_pinned_preserves_order(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.add_pinned("alice@example.com", "agent-1")
|
|
repo.add_pinned("alice@example.com", "agent-2")
|
|
repo.add_pinned("alice@example.com", "agent-3")
|
|
doc = repo.get("alice@example.com")
|
|
assert doc["agent_preferences"]["pinned"] == ["agent-1", "agent-2", "agent-3"]
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestRemovePinned:
|
|
def test_remove_pinned_single(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.add_pinned("alice@example.com", "agent-1")
|
|
repo.add_pinned("alice@example.com", "agent-2")
|
|
repo.remove_pinned("alice@example.com", "agent-1")
|
|
doc = repo.get("alice@example.com")
|
|
assert doc["agent_preferences"]["pinned"] == ["agent-2"]
|
|
|
|
def test_remove_pinned_missing_is_noop(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.add_pinned("alice@example.com", "agent-1")
|
|
repo.remove_pinned("alice@example.com", "agent-999")
|
|
doc = repo.get("alice@example.com")
|
|
assert doc["agent_preferences"]["pinned"] == ["agent-1"]
|
|
|
|
def test_remove_pinned_bulk(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
for i in range(5):
|
|
repo.add_pinned("alice@example.com", f"agent-{i}")
|
|
repo.remove_pinned_bulk("alice@example.com", ["agent-1", "agent-3", "agent-999"])
|
|
doc = repo.get("alice@example.com")
|
|
assert doc["agent_preferences"]["pinned"] == ["agent-0", "agent-2", "agent-4"]
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestSharedWithMe:
|
|
def test_add_shared_is_idempotent(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.add_shared("alice@example.com", "agent-x")
|
|
repo.add_shared("alice@example.com", "agent-x")
|
|
doc = repo.get("alice@example.com")
|
|
assert doc["agent_preferences"]["shared_with_me"] == ["agent-x"]
|
|
|
|
def test_remove_shared_bulk(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
for i in range(3):
|
|
repo.add_shared("alice@example.com", f"shared-{i}")
|
|
repo.remove_shared_bulk("alice@example.com", ["shared-0", "shared-2"])
|
|
doc = repo.get("alice@example.com")
|
|
assert doc["agent_preferences"]["shared_with_me"] == ["shared-1"]
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestRemoveAgentFromAll:
|
|
def test_removes_from_both_pinned_and_shared(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.add_pinned("alice@example.com", "agent-X")
|
|
repo.add_pinned("alice@example.com", "agent-keep")
|
|
repo.add_shared("alice@example.com", "agent-X")
|
|
repo.add_shared("alice@example.com", "agent-keep-2")
|
|
|
|
repo.remove_agent_from_all("alice@example.com", "agent-X")
|
|
|
|
doc = repo.get("alice@example.com")
|
|
assert doc["agent_preferences"]["pinned"] == ["agent-keep"]
|
|
assert doc["agent_preferences"]["shared_with_me"] == ["agent-keep-2"]
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestTenantIsolation:
|
|
"""Security-critical: operations on one user must never touch another's data."""
|
|
|
|
def test_add_pinned_does_not_leak_across_users(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.upsert("bob@example.com")
|
|
repo.add_pinned("alice@example.com", "agent-a")
|
|
repo.add_pinned("bob@example.com", "agent-b")
|
|
|
|
alice = repo.get("alice@example.com")
|
|
bob = repo.get("bob@example.com")
|
|
assert alice["agent_preferences"]["pinned"] == ["agent-a"]
|
|
assert bob["agent_preferences"]["pinned"] == ["agent-b"]
|
|
|
|
def test_remove_does_not_leak_across_users(self, repo):
|
|
repo.upsert("alice@example.com")
|
|
repo.upsert("bob@example.com")
|
|
repo.add_pinned("alice@example.com", "shared-agent-id")
|
|
repo.add_pinned("bob@example.com", "shared-agent-id")
|
|
|
|
repo.remove_pinned("alice@example.com", "shared-agent-id")
|
|
|
|
assert repo.get("alice@example.com")["agent_preferences"]["pinned"] == []
|
|
assert repo.get("bob@example.com")["agent_preferences"]["pinned"] == [
|
|
"shared-agent-id"
|
|
]
|