mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-18 12:13:46 +00:00
feat: teams
This commit is contained in:
1 parent
49fb28acd3
commit
d21f4758be
55 files changed
+5995
-169
No files matched your search
@@ -210,7 +210,9 @@ class TestAddChunk:
|
||||
response = AddChunk().post()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_source_not_found(self, app, pg_conn):
|
||||
def test_returns_403_inaccessible_source(self, app, pg_conn):
|
||||
# No ownership and no team editor grant resolves to None, which the
|
||||
# owner-or-editor gate answers as 403 "Source not accessible".
|
||||
from application.api.user.sources.chunks import AddChunk
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
@@ -223,7 +225,7 @@ class TestAddChunk:
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = AddChunk().post()
|
||||
assert response.status_code == 404
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_adds_chunk(self, app, pg_conn):
|
||||
from application.api.user.sources.chunks import AddChunk
|
||||
@@ -282,7 +284,9 @@ class TestDeleteChunk:
|
||||
response = DeleteChunk().delete()
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_returns_404_source_not_found(self, app, pg_conn):
|
||||
def test_returns_403_inaccessible_source(self, app, pg_conn):
|
||||
# No ownership and no team editor grant resolves to None, which the
|
||||
# owner-or-editor gate answers as 403 "Source not accessible".
|
||||
from application.api.user.sources.chunks import DeleteChunk
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
@@ -292,7 +296,7 @@ class TestDeleteChunk:
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = DeleteChunk().delete()
|
||||
assert response.status_code == 404
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_deletes_chunk(self, app, pg_conn):
|
||||
from application.api.user.sources.chunks import DeleteChunk
|
||||
@@ -359,7 +363,9 @@ class TestUpdateChunk:
|
||||
response = UpdateChunk().put()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_source_not_found(self, app, pg_conn):
|
||||
def test_returns_403_inaccessible_source(self, app, pg_conn):
|
||||
# No ownership and no team editor grant resolves to None, which the
|
||||
# owner-or-editor gate answers as 403 "Source not accessible".
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
@@ -372,7 +378,7 @@ class TestUpdateChunk:
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = UpdateChunk().put()
|
||||
assert response.status_code == 404
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_returns_404_chunk_not_found(self, app, pg_conn):
|
||||
from application.api.user.sources.chunks import UpdateChunk
|
||||
|
||||
@@ -512,7 +512,9 @@ class TestSyncSource:
|
||||
response = SyncSource().post()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_missing_source(self, app, pg_conn):
|
||||
def test_returns_403_inaccessible_source(self, app, pg_conn):
|
||||
# No ownership and no team editor grant resolves to None, which the
|
||||
# owner-or-editor gate answers as 403 "Source not accessible".
|
||||
from application.api.user.sources.routes import SyncSource
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
@@ -523,7 +525,7 @@ class TestSyncSource:
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = SyncSource().post()
|
||||
assert response.status_code == 404
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_returns_400_for_connector_type(self, app, pg_conn):
|
||||
from application.api.user.sources.routes import SyncSource
|
||||
@@ -659,7 +661,9 @@ class TestReingestSource:
|
||||
response = ReingestSource().post()
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_returns_404_missing_source(self, app, pg_conn):
|
||||
def test_returns_403_inaccessible_source(self, app, pg_conn):
|
||||
# No ownership and no team editor grant resolves to None, which the
|
||||
# owner-or-editor gate answers as 403 "Source not accessible".
|
||||
from application.api.user.sources.routes import ReingestSource
|
||||
|
||||
with _patch_db(pg_conn), app.test_request_context(
|
||||
@@ -670,7 +674,7 @@ class TestReingestSource:
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": "u"}
|
||||
response = ReingestSource().post()
|
||||
assert response.status_code == 404
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_triggers_reingest_task(self, app, pg_conn):
|
||||
from application.api.user.sources.routes import ReingestSource
|
||||
@@ -701,6 +705,58 @@ class TestReingestSource:
|
||||
f"reingest-source:{user}:{src['id']}"
|
||||
)
|
||||
|
||||
def test_team_editor_reingests_as_owner(self, app, pg_conn):
|
||||
"""A team editor (not the owner) can reingest a shared source; the
|
||||
task dispatches AS the real owner so the owner-scoped pipeline and
|
||||
the owner-agnostic vector partition stay consistent.
|
||||
"""
|
||||
import uuid
|
||||
|
||||
from application.api.user.sources.routes import ReingestSource
|
||||
from application.storage.db.repositories.team_members import (
|
||||
TeamMembersRepository,
|
||||
)
|
||||
from application.storage.db.repositories.team_resource_grants import (
|
||||
TeamResourceGrantsRepository,
|
||||
)
|
||||
from application.storage.db.repositories.teams import TeamsRepository
|
||||
|
||||
owner = "alice-reingest"
|
||||
editor = "bob-reingest"
|
||||
src = _seed_source(pg_conn, owner, name="shared-src", type="file")
|
||||
sid = str(src["id"])
|
||||
team = TeamsRepository(pg_conn).create(
|
||||
"Acme", f"acme-{uuid.uuid4().hex[:8]}", owner
|
||||
)
|
||||
TeamMembersRepository(pg_conn).add_member(
|
||||
team["id"], editor, role="team_member"
|
||||
)
|
||||
TeamResourceGrantsRepository(pg_conn).grant(
|
||||
team["id"], "source", sid, owner_id=owner, granted_by=owner,
|
||||
access_level="editor",
|
||||
)
|
||||
|
||||
fake_task = MagicMock(id="reingest-task-editor")
|
||||
with _patch_db(pg_conn), patch(
|
||||
"application.api.user.sources.routes.reingest_source_task.delay",
|
||||
return_value=fake_task,
|
||||
) as mock_delay, app.test_request_context(
|
||||
"/api/sources/reingest",
|
||||
method="POST",
|
||||
json={"source_id": sid},
|
||||
):
|
||||
from flask import request
|
||||
request.decoded_token = {"sub": editor}
|
||||
response = ReingestSource().post()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert mock_delay.call_args.kwargs["source_id"] == sid
|
||||
# Dispatched AS the owner, not the editor caller.
|
||||
assert mock_delay.call_args.kwargs["user"] == owner
|
||||
assert mock_delay.call_args.kwargs["idempotency_key"] == (
|
||||
f"reingest-source:{owner}:{sid}"
|
||||
)
|
||||
|
||||
def test_clears_stalled_ingest_progress_row(self, app, pg_conn):
|
||||
"""Reingest drops the stale chunk-progress row so the sources
|
||||
list stops deriving a 'failed' ingest status for the source.
|
||||
|
||||
Reference in new issue
Block a user