Connections own their credentials

Migration 0038 moves every stored secret (OAuth tokens, MCP OAuth
tokens and client registrations, API keys) into the connection's
encrypted envelope, links API-key tools to one connection per distinct
credential, allows several accounts per provider, and adds
credential_mode to sources and tools. OAuth MCP tools keep resolving
each member's own token, as they did before.

docsgpt.connectors.service is now the only reader of OAuth tokens:
get_valid_token_info refreshes under a row lock and persists rotated
refresh tokens, and a revoked grant flags the connection, pauses its
sources and notifies the owner. Loaders build from a connection
(BaseConnectorLoader.from_connection), so scheduled sync covers Drive,
SharePoint and Confluence sources with no browser. S3 and Reddit keys
stay on the connection instead of in remote_data.

New endpoints: POST /api/connections, /setup, /reconnect,
/picker-token, /claim, DELETE /api/connections/<id>, per-action
permissions and MCP refresh-tools. Upload, file listing, sync and
validate-session take a connection_id; session tokens keep working for
this release. The tool executor reads credentials from the resolved
connection (owner or member mode) and pauses on a Connect card when a
connection needs signing in. docsgpt connectors reencrypt rewrites
stored credentials after a key rotation.
This commit is contained in:
arc53-machine committed 2026-09-28 17:03:09 +01:00
1 parent 667d4bbab0
commit 3511004f50
54 files changed
+4007 -754

No files matched your search

@@ -3,7 +3,6 @@
from contextlib import contextmanager
from unittest.mock import patch
import pytest
@contextmanager
@@ -18,82 +17,3 @@ def _patch_db(conn):
yield
class TestGetTokenInfoFromSession:
def test_invalid_session_token_raises(self, pg_conn):
from docsgpt.parser.connectors.confluence.auth import (
ConfluenceAuth,
)
auth = ConfluenceAuth.__new__(ConfluenceAuth)
with _patch_db(pg_conn), pytest.raises(ValueError):
auth.get_token_info_from_session("no-such-token")
def test_missing_token_info_raises(self, pg_conn):
from docsgpt.parser.connectors.confluence.auth import (
ConfluenceAuth,
)
from docsgpt.storage.db.repositories.connector_sessions import (
ConnectorSessionsRepository,
)
repo = ConnectorSessionsRepository(pg_conn)
repo.upsert("u", "confluence", status="authorized")
# Set session_token but no token_info
session = repo.get_by_user_provider("u", "confluence")
repo.update(str(session["id"]), {"session_token": "tok-no-info"})
auth = ConfluenceAuth.__new__(ConfluenceAuth)
with _patch_db(pg_conn), pytest.raises(ValueError):
auth.get_token_info_from_session("tok-no-info")
def test_missing_required_fields_raises(self, pg_conn):
from docsgpt.parser.connectors.confluence.auth import (
ConfluenceAuth,
)
from docsgpt.storage.db.repositories.connector_sessions import (
ConnectorSessionsRepository,
)
repo = ConnectorSessionsRepository(pg_conn)
repo.upsert("u", "confluence", status="authorized")
session = repo.get_by_user_provider("u", "confluence")
repo.update(
str(session["id"]),
{
"session_token": "tok-partial",
"token_info": {"access_token": "at"}, # missing refresh + cloud_id
},
)
auth = ConfluenceAuth.__new__(ConfluenceAuth)
with _patch_db(pg_conn), pytest.raises(ValueError):
auth.get_token_info_from_session("tok-partial")
def test_complete_token_info_returned(self, pg_conn):
from docsgpt.parser.connectors.confluence.auth import (
ConfluenceAuth,
)
from docsgpt.storage.db.repositories.connector_sessions import (
ConnectorSessionsRepository,
)
repo = ConnectorSessionsRepository(pg_conn)
repo.upsert("u", "confluence", status="authorized")
session = repo.get_by_user_provider("u", "confluence")
repo.update(
str(session["id"]),
{
"session_token": "tok-good",
"token_info": {
"access_token": "at",
"refresh_token": "rt",
"cloud_id": "cid-1",
},
},
)
auth = ConfluenceAuth.__new__(ConfluenceAuth)
with _patch_db(pg_conn):
got = auth.get_token_info_from_session("tok-good")
assert got["access_token"] == "at"
assert got["cloud_id"] == "cid-1"
@@ -2,6 +2,7 @@
import os
import tempfile
from tests.parser.connectors.token_patch import patch_tokens
from unittest.mock import MagicMock, patch
import pytest
@@ -25,11 +26,12 @@ def _make_loader(token_info=None):
with patch("docsgpt.parser.connectors.confluence.loader.ConfluenceAuth") as MockAuth:
mock_auth = MagicMock()
mock_auth.get_token_info_from_session.return_value = token_info
token_info = token_info
MockAuth.return_value = mock_auth
from docsgpt.parser.connectors.confluence.loader import ConfluenceLoader
loader = ConfluenceLoader("session_tok")
with patch_tokens(token_info):
loader = ConfluenceLoader("session_tok")
loader.auth = mock_auth
return loader
@@ -69,7 +71,7 @@ class TestConfluenceLoaderInit:
def test_init_sets_attributes(self, loader):
assert loader.session_token == "session_tok"
assert loader.access_token == "test_at"
assert loader.refresh_token == "test_rt"
assert loader.connection_id == "conn-1"
assert loader.cloud_id == "test_cloud"
assert loader.next_page_token is None
assert "test_cloud" in loader.base_url
@@ -834,18 +836,16 @@ class TestRetryOnAuthFailure:
# Second call succeeds
return _mock_response({"results": [], "_links": {}})
loader.auth.refresh_access_token = MagicMock(return_value={
"access_token": "new_at",
"refresh_token": "new_rt",
})
loader._persist_refreshed_tokens = MagicMock()
with patch("requests.get", side_effect=flaky_request):
with patch(
"docsgpt.connectors.service.get_valid_token_info",
return_value={"access_token": "new_at", "cloud_id": "test_cloud"},
) as tokens, patch("requests.get", side_effect=flaky_request):
loader.load_data({})
assert loader.auth.refresh_access_token.called
# The rejected token goes to the service, which refreshes under the
# connection's row lock and stores the rotated refresh token.
tokens.assert_called_once_with("conn-1", rejected_access_token="test_at")
assert loader.access_token == "new_at"
assert loader._persist_refreshed_tokens.called
@pytest.mark.unit
def test_raises_non_auth_http_error(self, loader):
@@ -867,11 +867,9 @@ class TestRetryOnAuthFailure:
err = requests.exceptions.HTTPError(response=resp)
err.response = resp
loader.auth.refresh_access_token = MagicMock(
side_effect=Exception("refresh failed")
)
with patch("requests.get", side_effect=err):
with patch(
"docsgpt.connectors.service.get_valid_token_info", side_effect=Exception("refresh failed"),
), patch("requests.get", side_effect=err):
with pytest.raises(ValueError, match="Authentication failed"):
loader.load_data({})
@@ -890,29 +888,12 @@ class TestRetryOnAuthFailure:
raise err
return _mock_response({"results": [], "_links": {}})
loader.auth.refresh_access_token = MagicMock(return_value={
"access_token": "new_at",
"refresh_token": "new_rt",
})
loader._persist_refreshed_tokens = MagicMock()
with patch("requests.get", side_effect=flaky):
with patch(
"docsgpt.connectors.service.get_valid_token_info",
return_value={"access_token": "new_at", "cloud_id": "test_cloud"},
) as tokens, patch("requests.get", side_effect=flaky):
loader.load_data({})
assert loader.auth.refresh_access_token.called
assert tokens.called
# ---------------------------------------------------------------------------
# _persist_refreshed_tokens
# ---------------------------------------------------------------------------
class TestPersistRefreshedTokens:
pass
@pytest.mark.unit
def test_logs_warning_on_failure(self, loader):
loader.auth.sanitize_token_info = MagicMock(side_effect=Exception("db error"))
# Should not raise, just log a warning
loader._persist_refreshed_tokens({"access_token": "at"})
@@ -51,8 +51,7 @@ def _patches(session_return: Optional[Dict[str, Any]]):
)
return (
patch(
"docsgpt.storage.db.repositories.connector_sessions."
"ConnectorSessionsRepository",
"docsgpt.connectors.service.ConnectorSessionsRepository",
fake_repo_cls,
),
patch(
@@ -88,57 +87,18 @@ class TestSessionTokenFingerprint:
assert session_token_fingerprint("a") != session_token_fingerprint("b")
class TestConfluenceAuthDoesNotLeakToken:
class TestConnectionLookupDoesNotLeakToken:
"""A session token that names no connection is reported by fingerprint only."""
@pytest.mark.unit
def test_invalid_session_does_not_interpolate_token(self) -> None:
from docsgpt.parser.connectors.confluence.auth import ConfluenceAuth
from docsgpt.connectors import service
auth = ConfluenceAuth.__new__(ConfluenceAuth)
repo_patch, ctx_patch = _patches(None)
with repo_patch, ctx_patch:
with pytest.raises(ValueError) as excinfo:
auth.get_token_info_from_session(SECRET_TOKEN)
service.connection_id_for_session_token(SECRET_TOKEN)
message = str(excinfo.value)
assert SECRET_TOKEN not in message
assert session_token_fingerprint(SECRET_TOKEN) in message
class TestGoogleDriveAuthDoesNotLeakToken:
@pytest.mark.unit
def test_invalid_session_does_not_interpolate_token(self) -> None:
from docsgpt.parser.connectors.google_drive.auth import GoogleDriveAuth
auth = GoogleDriveAuth.__new__(GoogleDriveAuth)
repo_patch, ctx_patch = _patches(None)
with repo_patch, ctx_patch:
with pytest.raises(ValueError) as excinfo:
auth.get_token_info_from_session(SECRET_TOKEN)
# The Google Drive module wraps the inner ValueError in a broad
# ``except Exception as e: raise ValueError(... {str(e)})`` block,
# so the outer message still carries the fingerprint from the
# inner raise but must never carry the raw token.
message = str(excinfo.value)
assert SECRET_TOKEN not in message
assert session_token_fingerprint(SECRET_TOKEN) in message
class TestSharePointAuthDoesNotLeakToken:
@pytest.mark.unit
def test_invalid_session_does_not_interpolate_token(self) -> None:
from docsgpt.parser.connectors.share_point.auth import SharePointAuth
auth = SharePointAuth.__new__(SharePointAuth)
repo_patch, ctx_patch = _patches(None)
with repo_patch, ctx_patch:
with pytest.raises(ValueError) as excinfo:
auth.get_token_info_from_session(SECRET_TOKEN)
# SharePoint also wraps the inner ValueError. Same invariants.
message = str(excinfo.value)
assert SECRET_TOKEN not in message
assert session_token_fingerprint(SECRET_TOKEN) in message
@@ -1,5 +1,6 @@
"""Tests for ConnectorCreator factory class."""
from tests.parser.connectors.token_patch import patch_tokens
from unittest.mock import patch, MagicMock
import pytest
@@ -74,7 +75,7 @@ class TestConnectorCreator:
def test_create_connector_google_drive(self):
with patch("docsgpt.parser.connectors.google_drive.loader.GoogleDriveAuth") as MockAuth:
mock_auth_instance = MagicMock()
mock_auth_instance.get_token_info_from_session.return_value = {
token_info = {
"access_token": "at", "refresh_token": "rt"
}
mock_creds = MagicMock()
@@ -84,7 +85,8 @@ class TestConnectorCreator:
mock_auth_instance.build_drive_service.return_value = MagicMock()
MockAuth.return_value = mock_auth_instance
loader = self.ConnectorCreator.create_connector("google_drive", "session_tok")
with patch_tokens(token_info):
loader = self.ConnectorCreator.create_connector("google_drive", "session_tok")
from docsgpt.parser.connectors.google_drive.loader import GoogleDriveLoader
assert isinstance(loader, GoogleDriveLoader)
@@ -92,11 +94,12 @@ class TestConnectorCreator:
def test_create_connector_share_point(self):
with patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth") as MockAuth:
mock_auth_instance = MagicMock()
mock_auth_instance.get_token_info_from_session.return_value = {
token_info = {
"access_token": "at", "refresh_token": "rt"
}
MockAuth.return_value = mock_auth_instance
loader = self.ConnectorCreator.create_connector("share_point", "session_tok")
with patch_tokens(token_info):
loader = self.ConnectorCreator.create_connector("share_point", "session_tok")
from docsgpt.parser.connectors.share_point.loader import SharePointLoader
assert isinstance(loader, SharePointLoader)
@@ -364,71 +364,6 @@ class _FakeReadonlyCtx:
return False
class TestGetTokenInfoFromSession:
def _patches(self, session_return):
fake_repo_cls = type(
"FakeRepo",
(_FakeRepo,),
{"_session": session_return},
)
return (
patch(
"docsgpt.storage.db.repositories.connector_sessions.ConnectorSessionsRepository",
fake_repo_cls,
),
patch(
"docsgpt.storage.db.session.db_readonly",
lambda: _FakeReadonlyCtx(),
),
)
@pytest.mark.unit
def test_valid_session(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches({
"session_token": "st",
"token_info": {"access_token": "at", "refresh_token": "rt"},
})
with repo_patch, ctx_patch:
result = auth.get_token_info_from_session("st")
assert result["access_token"] == "at"
assert result["token_uri"] == "https://oauth2.googleapis.com/token"
@pytest.mark.unit
def test_session_not_found_raises(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches(None)
with repo_patch, ctx_patch:
with pytest.raises(ValueError, match="Failed to retrieve Google Drive token"):
auth.get_token_info_from_session("bad_token")
@pytest.mark.unit
def test_session_missing_token_info_raises(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches({"session_token": "st"})
with repo_patch, ctx_patch:
with pytest.raises(ValueError, match="Failed to retrieve Google Drive token"):
auth.get_token_info_from_session("st")
@pytest.mark.unit
def test_missing_required_fields_raises(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches({
"session_token": "st",
"token_info": {"access_token": "at"},
})
with repo_patch, ctx_patch:
with pytest.raises(ValueError, match="Failed to retrieve Google Drive token"):
auth.get_token_info_from_session("st")
@pytest.mark.unit
def test_empty_token_info_raises(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches({
"session_token": "st",
"token_info": None,
})
with repo_patch, ctx_patch:
with pytest.raises(ValueError, match="Failed to retrieve Google Drive token"):
auth.get_token_info_from_session("st")
class TestValidateCredentials:
@pytest.mark.unit
@@ -1,5 +1,6 @@
"""Tests for GoogleDriveLoader."""
from tests.parser.connectors.token_patch import patch_tokens
from unittest.mock import MagicMock, patch
import pytest
@@ -11,7 +12,7 @@ def _make_loader(service=None):
"""Create a GoogleDriveLoader with mocked dependencies."""
with patch("docsgpt.parser.connectors.google_drive.loader.GoogleDriveAuth") as MockAuth:
mock_auth = MagicMock()
mock_auth.get_token_info_from_session.return_value = {
token_info = {
"access_token": "at",
"refresh_token": "rt",
}
@@ -24,7 +25,8 @@ def _make_loader(service=None):
MockAuth.return_value = mock_auth
from docsgpt.parser.connectors.google_drive.loader import GoogleDriveLoader
loader = GoogleDriveLoader("session_tok")
with patch_tokens(token_info):
loader = GoogleDriveLoader("session_tok")
return loader
@@ -51,7 +53,7 @@ class TestGoogleDriveLoaderInit:
def test_init_service_failure_sets_none(self):
with patch("docsgpt.parser.connectors.google_drive.loader.GoogleDriveAuth") as MockAuth:
mock_auth = MagicMock()
mock_auth.get_token_info_from_session.return_value = {
token_info = {
"access_token": "at", "refresh_token": "rt"
}
mock_creds = MagicMock()
@@ -61,7 +63,8 @@ class TestGoogleDriveLoaderInit:
MockAuth.return_value = mock_auth
from docsgpt.parser.connectors.google_drive.loader import GoogleDriveLoader
loader = GoogleDriveLoader("st")
with patch_tokens(token_info):
loader = GoogleDriveLoader("st")
assert loader.service is None
@@ -285,63 +285,3 @@ class _FakeReadonlyCtx:
return False
class TestGetTokenInfoFromSession:
def _patches(self, session_return):
fake_repo_cls = type(
"FakeRepo",
(_FakeRepo,),
{"_session": session_return},
)
return (
patch(
"docsgpt.storage.db.repositories.connector_sessions.ConnectorSessionsRepository",
fake_repo_cls,
),
patch(
"docsgpt.storage.db.session.db_readonly",
lambda: _FakeReadonlyCtx(),
),
)
@pytest.mark.unit
def test_valid_session(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches({
"session_token": "st",
"token_info": {"access_token": "at", "refresh_token": "rt"},
})
with repo_patch, ctx_patch:
result = auth.get_token_info_from_session("st")
assert result["access_token"] == "at"
assert "token_uri" in result
@pytest.mark.unit
def test_session_not_found_raises(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches(None)
with repo_patch, ctx_patch:
with pytest.raises(ValueError, match="Failed to retrieve SharePoint token"):
auth.get_token_info_from_session("bad")
@pytest.mark.unit
def test_missing_token_info_raises(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches({"session_token": "st"})
with repo_patch, ctx_patch:
with pytest.raises(ValueError, match="Failed to retrieve SharePoint token"):
auth.get_token_info_from_session("st")
@pytest.mark.unit
def test_empty_token_info_raises(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches({"session_token": "st", "token_info": None})
with repo_patch, ctx_patch:
with pytest.raises(ValueError, match="Failed to retrieve SharePoint token"):
auth.get_token_info_from_session("st")
@pytest.mark.unit
def test_missing_required_fields_raises(self, auth, mock_settings):
repo_patch, ctx_patch = self._patches({
"session_token": "st",
"token_info": {"access_token": "at"},
})
with repo_patch, ctx_patch:
with pytest.raises(ValueError, match="Failed to retrieve SharePoint token"):
auth.get_token_info_from_session("st")
@@ -1,6 +1,7 @@
"""Tests for SharePointLoader."""
import os
from tests.parser.connectors.token_patch import patch_tokens
from unittest.mock import MagicMock, patch
import pytest
@@ -13,7 +14,7 @@ def _make_loader(access_token="at", refresh_token="rt", allows_shared=False):
"""Create a SharePointLoader with mocked dependencies."""
with patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth") as MockAuth:
mock_auth = MagicMock()
mock_auth.get_token_info_from_session.return_value = {
token_info = {
"access_token": access_token,
"refresh_token": refresh_token,
"allows_shared_content": allows_shared,
@@ -22,7 +23,8 @@ def _make_loader(access_token="at", refresh_token="rt", allows_shared=False):
MockAuth.return_value = mock_auth
from docsgpt.parser.connectors.share_point.loader import SharePointLoader
loader = SharePointLoader("session_tok")
with patch_tokens(token_info):
loader = SharePointLoader("session_tok")
return loader
@@ -42,7 +44,7 @@ class TestSharePointLoaderInit:
def test_init_sets_attributes(self, loader):
assert loader.session_token == "session_tok"
assert loader.access_token == "at"
assert loader.refresh_token == "rt"
assert loader.connection_id == "conn-1"
assert loader.allows_shared_content is False
assert loader.next_page_token is None
@@ -50,7 +52,7 @@ class TestSharePointLoaderInit:
def test_no_access_token_raises(self):
with patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth") as MockAuth:
mock_auth = MagicMock()
mock_auth.get_token_info_from_session.return_value = {
token_info = {
"access_token": None,
"refresh_token": "rt",
}
@@ -58,7 +60,8 @@ class TestSharePointLoaderInit:
from docsgpt.parser.connectors.share_point.loader import SharePointLoader
with pytest.raises(ValueError, match="No access token"):
SharePointLoader("st")
with patch_tokens(token_info):
SharePointLoader("st")
class TestGetHeaders:
@@ -86,16 +89,19 @@ class TestEnsureValidToken:
@pytest.mark.unit
def test_expired_token_refreshes(self, loader):
loader.auth.is_token_expired.return_value = True
loader.auth.refresh_access_token.return_value = {"access_token": "new_at"}
loader._ensure_valid_token()
with patch(
"docsgpt.connectors.service.get_valid_token_info", return_value={"access_token": "new_at"},
) as tokens:
loader._ensure_valid_token()
assert loader.access_token == "new_at"
tokens.assert_called_once_with("conn-1")
@pytest.mark.unit
def test_refresh_failure_raises(self, loader):
loader.auth.is_token_expired.return_value = True
loader.auth.refresh_access_token.side_effect = Exception("fail")
with pytest.raises(ValueError, match="Failed to refresh"):
loader._ensure_valid_token()
with patch("docsgpt.connectors.service.get_valid_token_info", side_effect=Exception("fail")):
with pytest.raises(ValueError, match="Failed to refresh"):
loader._ensure_valid_token()
class TestGetItemUrl:
@@ -305,7 +311,7 @@ class TestLoadFileById:
return mock_resp_ok
loader._process_file = MagicMock(return_value=Document(text="", doc_id="f1", extra_info={}))
loader.auth.refresh_access_token.return_value = {"access_token": "new_at"}
loader._refresh_rejected_token = MagicMock(return_value={"access_token": "new_at"})
with patch("docsgpt.parser.connectors.share_point.loader.requests.get", side_effect=get_side_effect):
doc = loader._load_file_by_id("f1")
@@ -481,7 +487,7 @@ class TestDownloadFileContent:
raise http_error
return mock_resp_ok
loader.auth.refresh_access_token.return_value = {"access_token": "new_at"}
loader._refresh_rejected_token = MagicMock(return_value={"access_token": "new_at"})
with patch("docsgpt.parser.connectors.share_point.loader.requests.get", side_effect=get_side_effect):
content = loader._download_file_content("f1")
@@ -1004,7 +1010,7 @@ class TestRetryOnAuthFailureDecorator:
raise http_error
return mock_resp_ok
loader.auth.refresh_access_token.return_value = {"access_token": "new_at"}
loader._refresh_rejected_token = MagicMock(return_value={"access_token": "new_at"})
with patch("docsgpt.parser.connectors.share_point.loader.requests.get", side_effect=get_side_effect):
content = loader._download_file_content("f1")
@@ -1018,7 +1024,7 @@ class TestRetryOnAuthFailureDecorator:
mock_resp_403.status_code = 403
http_error = real_requests.exceptions.HTTPError(response=mock_resp_403)
loader.auth.refresh_access_token.side_effect = Exception("refresh fail")
loader._refresh_rejected_token = MagicMock(side_effect=Exception("refresh fail"))
with patch("docsgpt.parser.connectors.share_point.loader.requests.get", side_effect=http_error):
with pytest.raises(ValueError, match="could not be refreshed"):
+23
View File
@@ -0,0 +1,23 @@
"""Stand-in for the connection service in connector loader tests."""
from __future__ import annotations
from contextlib import ExitStack, contextmanager
from unittest.mock import patch
@contextmanager
def patch_tokens(token_info: dict, connection_id: str = "conn-1"):
"""Make every loader resolve ``connection_id`` and read ``token_info``.
Yields the ``get_valid_token_info`` mock so a test can assert on or
change what a refresh returns.
"""
with ExitStack() as stack:
stack.enter_context(
patch("docsgpt.connectors.service.connection_id_for_session_token", return_value=connection_id)
)
tokens = stack.enter_context(
patch("docsgpt.connectors.service.get_valid_token_info", return_value=token_info)
)
yield tokens
@@ -66,7 +66,8 @@ class TestSharePointLoaderLoadFileById:
"""Test _load_file_by_id method."""
@patch("docsgpt.parser.connectors.share_point.loader.requests.get")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.get_token_info_from_session")
@patch("docsgpt.connectors.service.connection_id_for_session_token", new=lambda token: "conn-1")
@patch("docsgpt.connectors.service.get_valid_token_info")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.__init__", return_value=None)
@patch("docsgpt.parser.connectors.share_point.loader.SharePointLoader._ensure_valid_token")
def test_load_file_by_id_includes_size_in_select(self, mock_ensure_token, mock_auth_init, mock_get_token, mock_get):
@@ -97,7 +98,8 @@ class TestSharePointLoaderLoadFileById:
assert "size" in params["$select"]
@patch("docsgpt.parser.connectors.share_point.loader.requests.get")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.get_token_info_from_session")
@patch("docsgpt.connectors.service.connection_id_for_session_token", new=lambda token: "conn-1")
@patch("docsgpt.connectors.service.get_valid_token_info")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.__init__", return_value=None)
@patch("docsgpt.parser.connectors.share_point.loader.SharePointLoader._ensure_valid_token")
def test_load_file_by_id_returns_document_with_size(self, mock_ensure_token, mock_auth_init, mock_get_token, mock_get):
@@ -134,7 +136,8 @@ class TestSharePointLoaderListItems:
"""Test _list_items_in_parent method."""
@patch("docsgpt.parser.connectors.share_point.loader.requests.get")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.get_token_info_from_session")
@patch("docsgpt.connectors.service.connection_id_for_session_token", new=lambda token: "conn-1")
@patch("docsgpt.connectors.service.get_valid_token_info")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.__init__", return_value=None)
@patch("docsgpt.parser.connectors.share_point.loader.SharePointLoader._ensure_valid_token")
def test_list_items_includes_size_in_select(self, mock_ensure_token, mock_auth_init, mock_get_token, mock_get):
@@ -169,7 +172,8 @@ class TestSharePointLoaderListItems:
assert "size" in params["$select"]
@patch("docsgpt.parser.connectors.share_point.loader.requests.get")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.get_token_info_from_session")
@patch("docsgpt.connectors.service.connection_id_for_session_token", new=lambda token: "conn-1")
@patch("docsgpt.connectors.service.get_valid_token_info")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.__init__", return_value=None)
@patch("docsgpt.parser.connectors.share_point.loader.SharePointLoader._ensure_valid_token")
def test_list_items_folders_include_size(self, mock_ensure_token, mock_auth_init, mock_get_token, mock_get):