Files
wehub-resource-sync fed8b2eed7
Build and push multi-arch DocsGPT Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Has been cancelled
Backend release / release (push) Has been cancelled
Bandit Security Scan / bandit_scan (push) Has been cancelled
Build and push multi-arch DocsGPT Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Has been cancelled
Build and push multi-arch DocsGPT Docker image / manifest (push) Has been cancelled
Build and push DocsGPT FE Docker image for development / build (linux/amd64, ubuntu-latest, amd64) (push) Has been cancelled
Build and push DocsGPT FE Docker image for development / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Has been cancelled
Build and push DocsGPT FE Docker image for development / manifest (push) Has been cancelled
Python linting / ruff (push) Has been cancelled
Run python tests with pytest / Run tests and count coverage (3.12) (push) Has been cancelled
React Widget Build / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:28:29 +08:00

1037 lines
41 KiB
Python

"""Tests for SharePointLoader."""
import os
from unittest.mock import MagicMock, patch
import pytest
import requests as real_requests
from application.parser.schema.base import Document
def _make_loader(access_token="at", refresh_token="rt", allows_shared=False):
"""Create a SharePointLoader with mocked dependencies."""
with patch("application.parser.connectors.share_point.loader.SharePointAuth") as MockAuth:
mock_auth = MagicMock()
mock_auth.get_token_info_from_session.return_value = {
"access_token": access_token,
"refresh_token": refresh_token,
"allows_shared_content": allows_shared,
}
mock_auth.is_token_expired.return_value = False
MockAuth.return_value = mock_auth
from application.parser.connectors.share_point.loader import SharePointLoader
loader = SharePointLoader("session_tok")
return loader
@pytest.fixture
def loader():
return _make_loader()
@pytest.fixture
def loader_shared():
return _make_loader(allows_shared=True)
class TestSharePointLoaderInit:
@pytest.mark.unit
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.allows_shared_content is False
assert loader.next_page_token is None
@pytest.mark.unit
def test_no_access_token_raises(self):
with patch("application.parser.connectors.share_point.loader.SharePointAuth") as MockAuth:
mock_auth = MagicMock()
mock_auth.get_token_info_from_session.return_value = {
"access_token": None,
"refresh_token": "rt",
}
MockAuth.return_value = mock_auth
from application.parser.connectors.share_point.loader import SharePointLoader
with pytest.raises(ValueError, match="No access token"):
SharePointLoader("st")
class TestGetHeaders:
@pytest.mark.unit
def test_returns_bearer_token(self, loader):
headers = loader._get_headers()
assert headers["Authorization"] == "Bearer at"
assert headers["Accept"] == "application/json"
class TestEnsureValidToken:
@pytest.mark.unit
def test_valid_token_no_op(self, loader):
loader.auth.is_token_expired.return_value = False
loader._ensure_valid_token()
@pytest.mark.unit
def test_no_access_token_raises(self, loader):
loader.access_token = None
with pytest.raises(ValueError, match="No access token"):
loader._ensure_valid_token()
@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()
assert loader.access_token == "new_at"
@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()
class TestGetItemUrl:
@pytest.mark.unit
def test_simple_id(self, loader):
url = loader._get_item_url("item123")
assert url == f"{loader.GRAPH_API_BASE}/me/drive/items/item123"
@pytest.mark.unit
def test_drive_colon_item(self, loader):
url = loader._get_item_url("driveABC:itemXYZ")
assert url == f"{loader.GRAPH_API_BASE}/drives/driveABC/items/itemXYZ"
class TestResolveMimeType:
@pytest.mark.unit
def test_supported_mime_from_file_data(self, loader):
resource = {"file": {"mimeType": "application/pdf"}, "name": "test.pdf"}
mime, supported = loader._resolve_mime_type(resource)
assert mime == "application/pdf"
assert supported is True
@pytest.mark.unit
def test_fallback_to_extension(self, loader):
resource = {"file": {"mimeType": "application/octet-stream"}, "name": "test.docx"}
mime, supported = loader._resolve_mime_type(resource)
assert mime == "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
assert supported is True
@pytest.mark.unit
def test_unsupported_returns_false(self, loader):
resource = {"file": {"mimeType": "application/zip"}, "name": "test.zip"}
mime, supported = loader._resolve_mime_type(resource)
assert supported is False
@pytest.mark.unit
def test_no_file_data_fallback_to_extension(self, loader):
resource = {"name": "readme.txt"}
mime, supported = loader._resolve_mime_type(resource)
assert mime == "text/plain"
assert supported is True
@pytest.mark.unit
def test_no_file_data_no_extension(self, loader):
resource = {"name": "noext"}
mime, supported = loader._resolve_mime_type(resource)
assert mime == "application/octet-stream"
assert supported is False
class TestProcessFile:
@pytest.mark.unit
def test_supported_file_with_content(self, loader):
loader._download_file_content = MagicMock(return_value="content")
metadata = {
"id": "f1",
"name": "test.pdf",
"file": {"mimeType": "application/pdf"},
"size": 1024,
"createdDateTime": "2025-01-01",
"lastModifiedDateTime": "2025-01-02",
}
doc = loader._process_file(metadata, load_content=True)
assert doc is not None
assert doc.text == "content"
assert doc.extra_info["source"] == "share_point"
@pytest.mark.unit
def test_supported_file_no_content(self, loader):
metadata = {
"id": "f1",
"name": "test.pdf",
"file": {"mimeType": "application/pdf"},
}
doc = loader._process_file(metadata, load_content=False)
assert doc.text == ""
@pytest.mark.unit
def test_unsupported_returns_none(self, loader):
metadata = {
"id": "f1",
"name": "test.zip",
"file": {"mimeType": "application/zip"},
}
doc = loader._process_file(metadata)
assert doc is None
@pytest.mark.unit
def test_download_failure_returns_none(self, loader):
loader._download_file_content = MagicMock(return_value=None)
metadata = {
"id": "f1",
"name": "test.txt",
"file": {"mimeType": "text/plain"},
}
doc = loader._process_file(metadata, load_content=True)
assert doc is None
@pytest.mark.unit
def test_exception_returns_none(self, loader):
loader._download_file_content = MagicMock(side_effect=Exception("fail"))
metadata = {"id": "f1", "name": "test.txt", "file": {"mimeType": "text/plain"}}
doc = loader._process_file(metadata, load_content=True)
assert doc is None
class TestLoadData:
@pytest.mark.unit
def test_load_specific_files(self, loader):
doc = Document(text="c", doc_id="f1", extra_info={"file_name": "test.pdf"})
loader._load_file_by_id = MagicMock(return_value=doc)
result = loader.load_data({"file_ids": ["f1"]})
assert len(result) == 1
@pytest.mark.unit
def test_load_files_with_search_filter(self, loader):
doc = Document(text="c", doc_id="f1", extra_info={"file_name": "report.pdf"})
loader._load_file_by_id = MagicMock(return_value=doc)
result = loader.load_data({"file_ids": ["f1"], "search_query": "report"})
assert len(result) == 1
result = loader.load_data({"file_ids": ["f1"], "search_query": "other"})
assert len(result) == 0
@pytest.mark.unit
def test_load_files_error_continues(self, loader):
loader._load_file_by_id = MagicMock(side_effect=Exception("fail"))
result = loader.load_data({"file_ids": ["f1"]})
assert len(result) == 0
@pytest.mark.unit
def test_shared_mode(self, loader_shared):
loader_shared._list_shared_items = MagicMock(return_value=[
Document(text="", doc_id="s1", extra_info={})
])
result = loader_shared.load_data({"shared": True})
assert len(result) == 1
@pytest.mark.unit
def test_shared_mode_personal_account_returns_empty(self, loader):
result = loader.load_data({"shared": True})
assert len(result) == 0
@pytest.mark.unit
def test_browse_mode_uses_list_items(self, loader):
loader._list_items_in_parent = MagicMock(return_value=[])
loader.load_data({"folder_id": "folder1"})
loader._list_items_in_parent.assert_called_once()
@pytest.mark.unit
def test_browse_mode_defaults_to_root(self, loader):
loader._list_items_in_parent = MagicMock(return_value=[])
loader.load_data({})
args = loader._list_items_in_parent.call_args
assert args[0][0] == "root"
@pytest.mark.unit
def test_list_only_mode(self, loader):
loader._list_items_in_parent = MagicMock(return_value=[])
loader.load_data({"list_only": True})
args = loader._list_items_in_parent.call_args
assert args[1]["load_content"] is False
@pytest.mark.unit
def test_outer_exception_raises(self, loader):
"""The outer try/except in load_data re-raises unexpected errors."""
loader._list_items_in_parent = MagicMock(side_effect=RuntimeError("unexpected"))
with pytest.raises(RuntimeError, match="unexpected"):
loader.load_data({})
class TestLoadFileById:
@pytest.mark.unit
def test_loads_and_processes(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {
"id": "f1", "name": "test.pdf", "file": {"mimeType": "application/pdf"}
}
mock_response.raise_for_status = MagicMock()
loader._process_file = MagicMock(return_value=Document(text="", doc_id="f1", extra_info={}))
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
doc = loader._load_file_by_id("f1")
assert doc is not None
@pytest.mark.unit
def test_http_error_retries_with_refresh(self, loader):
mock_resp_401 = MagicMock()
mock_resp_401.status_code = 401
http_error = real_requests.exceptions.HTTPError(response=mock_resp_401)
mock_resp_ok = MagicMock()
mock_resp_ok.json.return_value = {"id": "f1", "name": "t.pdf", "file": {"mimeType": "application/pdf"}}
mock_resp_ok.raise_for_status = MagicMock()
call_count = [0]
def get_side_effect(*args, **kwargs):
call_count[0] += 1
if call_count[0] == 1:
raise http_error
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"}
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=get_side_effect):
doc = loader._load_file_by_id("f1")
assert doc is not None
@pytest.mark.unit
def test_general_exception_returns_none(self, loader):
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=Exception("fail")):
doc = loader._load_file_by_id("f1")
assert doc is None
class TestListItemsInParent:
@pytest.mark.unit
def test_lists_files_and_folders(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [
{"id": "folder1", "name": "Docs", "folder": {}, "size": 0, "createdDateTime": "d1", "lastModifiedDateTime": "d2"},
{"id": "file1", "name": "test.pdf", "file": {"mimeType": "application/pdf"}, "size": 100},
],
}
mock_response.raise_for_status = MagicMock()
loader._process_file = MagicMock(return_value=Document(text="", doc_id="file1", extra_info={}))
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
docs = loader._list_items_in_parent("root")
assert len(docs) == 2
assert docs[0].extra_info["is_folder"] is True
@pytest.mark.unit
def test_search_query_uses_search_url(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {"value": []}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response) as mock_get:
loader._list_items_in_parent("root", search_query="report")
call_url = mock_get.call_args[0][0]
assert "search" in call_url
@pytest.mark.unit
def test_search_with_drive_id(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {"value": []}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response) as mock_get:
loader._list_items_in_parent("drive1:folder1", search_query="test")
call_url = mock_get.call_args[0][0]
assert "drives/drive1" in call_url
@pytest.mark.unit
def test_pagination_token_stored(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [
{"id": "f1", "name": "test.txt", "file": {"mimeType": "text/plain"}},
],
"@odata.nextLink": "https://graph.microsoft.com/v1.0/items?$skiptoken=abc123",
}
mock_response.raise_for_status = MagicMock()
loader._process_file = MagicMock(return_value=Document(text="", doc_id="f1", extra_info={}))
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
loader._list_items_in_parent("root")
assert loader.next_page_token == "abc123"
@pytest.mark.unit
def test_no_next_link_clears_token(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {"value": []}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
loader._list_items_in_parent("root")
assert loader.next_page_token is None
@pytest.mark.unit
def test_limit_stops_early(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [
{"id": f"f{i}", "name": f"f{i}.txt", "file": {"mimeType": "text/plain"}} for i in range(10)
],
}
mock_response.raise_for_status = MagicMock()
loader._process_file = MagicMock(side_effect=lambda m, **kw: Document(text="", doc_id=m["id"], extra_info={}))
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
docs = loader._list_items_in_parent("root", limit=3)
assert len(docs) == 3
@pytest.mark.unit
def test_page_token_passed_as_skip_token(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {"value": []}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response) as mock_get:
loader._list_items_in_parent("root", page_token="tok123")
call_kwargs = mock_get.call_args[1]
assert call_kwargs["params"]["$skipToken"] == "tok123"
@pytest.mark.unit
def test_exception_returns_partial(self, loader):
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=Exception("fail")):
docs = loader._list_items_in_parent("root")
assert docs == []
@pytest.mark.unit
def test_next_link_without_skiptoken(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [],
"@odata.nextLink": "https://graph.microsoft.com/v1.0/items?other=param",
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
loader._list_items_in_parent("root")
assert loader.next_page_token is None
class TestDownloadFileContent:
@pytest.mark.unit
def test_downloads_and_decodes(self, loader):
mock_response = MagicMock()
mock_response.content = b"file content"
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
content = loader._download_file_content("f1")
assert content == "file content"
@pytest.mark.unit
def test_unicode_decode_error_returns_none(self, loader):
mock_response = MagicMock()
mock_response.content = MagicMock()
mock_response.content.decode.side_effect = UnicodeDecodeError("utf-8", b"", 0, 1, "bad")
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
result = loader._download_file_content("f1")
assert result is None
@pytest.mark.unit
def test_general_exception_returns_none(self, loader):
mock_response = MagicMock()
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=Exception("fail")):
result = loader._download_file_content("f1")
assert result is None
@pytest.mark.unit
def test_http_401_retries_with_refresh(self, loader):
mock_resp_401 = MagicMock()
mock_resp_401.status_code = 401
http_error = real_requests.exceptions.HTTPError(response=mock_resp_401)
mock_resp_ok = MagicMock()
mock_resp_ok.content = b"content after refresh"
mock_resp_ok.raise_for_status = MagicMock()
call_count = [0]
def get_side_effect(*args, **kwargs):
call_count[0] += 1
if call_count[0] == 1:
raise http_error
return mock_resp_ok
loader.auth.refresh_access_token.return_value = {"access_token": "new_at"}
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=get_side_effect):
content = loader._download_file_content("f1")
assert content == "content after refresh"
class TestListSharedItems:
@pytest.mark.unit
def test_lists_shared_files(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value="https://user.sharepoint.com/drive")
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{
"hitsContainers": [{
"total": 2,
"hits": [
{
"resource": {
"id": "item1",
"name": "shared.pdf",
"file": {"mimeType": "application/pdf"},
"size": 100,
"parentReference": {"driveId": "d1"},
}
},
{
"resource": {
"id": "item2",
"name": "folder1",
"folder": {},
"parentReference": {"driveId": "d1"},
}
},
],
}]
}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items(limit=100, load_content=False)
assert len(docs) == 2
@pytest.mark.unit
def test_deduplicates_hits(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{
"hitsContainers": [{
"total": 2,
"hits": [
{"resource": {"id": "item1", "name": "f.pdf", "file": {"mimeType": "application/pdf"}, "parentReference": {"driveId": "d1"}}},
{"resource": {"id": "item1", "name": "f.pdf", "file": {"mimeType": "application/pdf"}, "parentReference": {"driveId": "d1"}}},
],
}]
}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items(limit=100, load_content=False)
assert len(docs) == 1
@pytest.mark.unit
def test_pagination_with_offset(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
hits = [
{"resource": {"id": f"item{i}", "name": f"f{i}.pdf", "file": {"mimeType": "application/pdf"}, "parentReference": {"driveId": "d1"}}}
for i in range(5)
]
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{"hitsContainers": [{"total": 5, "hits": hits}]}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items(limit=2, page_token="2", load_content=False)
assert len(docs) == 2
assert loader_shared.next_page_token == "4"
@pytest.mark.unit
def test_offset_beyond_results_returns_empty(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{"hitsContainers": [{"total": 1, "hits": [
{"resource": {"id": "item1", "name": "f.pdf", "file": {"mimeType": "application/pdf"}, "parentReference": {"driveId": "d1"}}}
]}]}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items(page_token="999")
assert len(docs) == 0
assert loader_shared.next_page_token is None
@pytest.mark.unit
def test_invalid_page_token_defaults_to_zero(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{"hitsContainers": [{"total": 1, "hits": [
{"resource": {"id": "item1", "name": "f.pdf", "file": {"mimeType": "application/pdf"}, "parentReference": {"driveId": "d1"}}}
]}]}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items(page_token="invalid", load_content=False)
assert len(docs) == 1
@pytest.mark.unit
def test_negative_page_token_defaults_to_zero(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{"hitsContainers": [{"total": 1, "hits": [
{"resource": {"id": "item1", "name": "f.pdf", "file": {"mimeType": "application/pdf"}, "parentReference": {"driveId": "d1"}}}
]}]}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items(page_token="-5", load_content=False)
assert len(docs) == 1
@pytest.mark.unit
def test_empty_search_response(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
mock_response = MagicMock()
mock_response.json.return_value = {"value": []}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items()
assert len(docs) == 0
@pytest.mark.unit
def test_empty_hits_containers(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{"hitsContainers": []}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items()
assert len(docs) == 0
@pytest.mark.unit
def test_unsupported_shared_file_skipped(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{"hitsContainers": [{"total": 1, "hits": [
{"resource": {"id": "item1", "name": "f.zip", "file": {"mimeType": "application/zip"}, "parentReference": {"driveId": "d1"}}}
]}]}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items(load_content=False)
assert len(docs) == 0
@pytest.mark.unit
def test_shared_item_with_content_download(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
loader_shared._download_file_content = MagicMock(return_value="content")
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{"hitsContainers": [{"total": 1, "hits": [
{"resource": {"id": "item1", "name": "f.pdf", "file": {"mimeType": "application/pdf"}, "parentReference": {"driveId": "d1"}}}
]}]}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
docs = loader_shared._list_shared_items(load_content=True)
assert len(docs) == 1
assert docs[0].text == "content"
@pytest.mark.unit
def test_exception_returns_empty(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
with patch("application.parser.connectors.share_point.loader.requests.post", side_effect=Exception("fail")):
docs = loader_shared._list_shared_items()
assert docs == []
@pytest.mark.unit
def test_last_page_clears_next_token(self, loader_shared):
loader_shared._get_user_drive_web_url = MagicMock(return_value=None)
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [{"hitsContainers": [{"total": 1, "hits": [
{"resource": {"id": "item1", "name": "f.pdf", "file": {"mimeType": "application/pdf"}, "parentReference": {"driveId": "d1"}}}
]}]}]
}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.post", return_value=mock_response):
loader_shared._list_shared_items(limit=100, load_content=False)
assert loader_shared.next_page_token is None
class TestGetUserDriveWebUrl:
@pytest.mark.unit
def test_returns_web_url(self, loader):
mock_response = MagicMock()
mock_response.json.return_value = {"webUrl": "https://user.sharepoint.com/drive"}
mock_response.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
url = loader._get_user_drive_web_url()
assert url == "https://user.sharepoint.com/drive"
@pytest.mark.unit
def test_exception_returns_none(self, loader):
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=Exception("fail")):
url = loader._get_user_drive_web_url()
assert url is None
class TestBuildSharedKqlQuery:
@pytest.mark.unit
def test_with_search_and_drive_url(self, loader):
query = loader._build_shared_kql_query("test", "https://drive.url")
assert 'test AND -path:"https://drive.url"' == query
@pytest.mark.unit
def test_with_search_no_drive_url(self, loader):
query = loader._build_shared_kql_query("test", None)
assert query == "test"
@pytest.mark.unit
def test_no_search_with_drive_url(self, loader):
query = loader._build_shared_kql_query(None, "https://drive.url")
assert '* AND -path:"https://drive.url"' == query
@pytest.mark.unit
def test_no_search_no_drive_url(self, loader):
query = loader._build_shared_kql_query(None, None)
assert query == "*"
class TestDownloadToDirectory:
@pytest.mark.unit
def test_download_files(self, loader, tmp_path):
loader._download_file_to_directory = MagicMock(return_value=True)
result = loader.download_to_directory(str(tmp_path), {"file_ids": ["f1", "f2"]})
assert result["files_downloaded"] == 2
assert result["source_type"] == "share_point"
@pytest.mark.unit
def test_download_files_string_id(self, loader, tmp_path):
loader._download_file_to_directory = MagicMock(return_value=True)
result = loader.download_to_directory(str(tmp_path), {"file_ids": "single"})
assert result["files_downloaded"] == 1
@pytest.mark.unit
def test_download_folders(self, loader, tmp_path):
mock_response = MagicMock()
mock_response.json.return_value = {"name": "MyFolder"}
mock_response.raise_for_status = MagicMock()
loader._download_folder_recursive = MagicMock(return_value=3)
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
result = loader.download_to_directory(str(tmp_path), {"folder_ids": ["folder1"]})
assert result["files_downloaded"] == 3
@pytest.mark.unit
def test_download_folders_string_id(self, loader, tmp_path):
mock_response = MagicMock()
mock_response.json.return_value = {"name": "F"}
mock_response.raise_for_status = MagicMock()
loader._download_folder_recursive = MagicMock(return_value=1)
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
result = loader.download_to_directory(str(tmp_path), {"folder_ids": "single_folder"})
assert result["files_downloaded"] == 1
@pytest.mark.unit
def test_no_ids_returns_error(self, loader, tmp_path):
result = loader.download_to_directory(str(tmp_path), {})
assert "error" in result
@pytest.mark.unit
def test_none_config(self, loader, tmp_path):
result = loader.download_to_directory(str(tmp_path))
assert "error" in result
@pytest.mark.unit
def test_folder_error_continues(self, loader, tmp_path):
mock_response = MagicMock()
mock_response.raise_for_status.side_effect = Exception("fail")
loader._download_file_to_directory = MagicMock(return_value=True)
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
result = loader.download_to_directory(str(tmp_path), {"file_ids": ["f1"], "folder_ids": ["bad"]})
assert result["files_downloaded"] == 1
class TestDownloadSingleFile:
@pytest.mark.unit
def test_downloads_supported_file(self, loader, tmp_path):
mock_meta_resp = MagicMock()
mock_meta_resp.json.return_value = {
"name": "test.pdf", "file": {"mimeType": "application/pdf"}
}
mock_meta_resp.raise_for_status = MagicMock()
mock_dl_resp = MagicMock()
mock_dl_resp.content = b"pdf content"
mock_dl_resp.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=[mock_meta_resp, mock_dl_resp]):
result = loader._download_single_file("f1", str(tmp_path))
assert result is True
assert os.path.exists(os.path.join(str(tmp_path), "test.pdf"))
@pytest.mark.unit
def test_unsupported_returns_false(self, loader, tmp_path):
mock_resp = MagicMock()
mock_resp.json.return_value = {
"name": "test.zip", "file": {"mimeType": "application/zip"}
}
mock_resp.raise_for_status = MagicMock()
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_resp):
result = loader._download_single_file("f1", str(tmp_path))
assert result is False
@pytest.mark.unit
def test_exception_returns_false(self, loader, tmp_path):
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=Exception("fail")):
result = loader._download_single_file("f1", str(tmp_path))
assert result is False
class TestDownloadFolderRecursive:
@pytest.mark.unit
def test_downloads_files_in_folder(self, loader, tmp_path):
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [
{"id": "f1", "name": "file1.txt"},
],
}
mock_response.raise_for_status = MagicMock()
loader._download_single_file = MagicMock(return_value=True)
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
count = loader._download_folder_recursive("folder1", str(tmp_path))
assert count == 1
@pytest.mark.unit
def test_recurses_into_subfolders(self, loader, tmp_path):
call_count = [0]
def get_side_effect(*args, **kwargs):
call_count[0] += 1
resp = MagicMock()
resp.raise_for_status = MagicMock()
if call_count[0] == 1:
resp.json.return_value = {
"value": [
{"id": "sub1", "name": "subfolder", "folder": {}},
{"id": "f1", "name": "file1.txt"},
],
}
else:
resp.json.return_value = {
"value": [
{"id": "f2", "name": "file2.txt"},
],
}
return resp
loader._download_single_file = MagicMock(return_value=True)
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=get_side_effect):
count = loader._download_folder_recursive("folder1", str(tmp_path), recursive=True)
assert count == 2
@pytest.mark.unit
def test_non_recursive_skips_subfolders(self, loader, tmp_path):
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [
{"id": "sub1", "name": "subfolder", "folder": {}},
{"id": "f1", "name": "file1.txt"},
],
}
mock_response.raise_for_status = MagicMock()
loader._download_single_file = MagicMock(return_value=True)
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
count = loader._download_folder_recursive("folder1", str(tmp_path), recursive=False)
assert count == 1
@pytest.mark.unit
def test_download_failure_continues(self, loader, tmp_path):
mock_response = MagicMock()
mock_response.json.return_value = {
"value": [
{"id": "f1", "name": "fail.txt"},
{"id": "f2", "name": "ok.txt"},
],
}
mock_response.raise_for_status = MagicMock()
loader._download_single_file = MagicMock(side_effect=[False, True])
with patch("application.parser.connectors.share_point.loader.requests.get", return_value=mock_response):
count = loader._download_folder_recursive("folder1", str(tmp_path))
assert count == 1
@pytest.mark.unit
def test_pagination_follows_next_link(self, loader, tmp_path):
call_count = [0]
def get_side_effect(*args, **kwargs):
call_count[0] += 1
resp = MagicMock()
resp.raise_for_status = MagicMock()
if call_count[0] == 1:
resp.json.return_value = {
"value": [{"id": "f1", "name": "file1.txt"}],
"@odata.nextLink": "https://graph.microsoft.com/next",
}
else:
resp.json.return_value = {
"value": [{"id": "f2", "name": "file2.txt"}],
}
return resp
loader._download_single_file = MagicMock(return_value=True)
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=get_side_effect):
count = loader._download_folder_recursive("folder1", str(tmp_path))
assert count == 2
@pytest.mark.unit
def test_exception_returns_partial(self, loader, tmp_path):
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=Exception("fail")):
count = loader._download_folder_recursive("folder1", str(tmp_path))
assert count == 0
class TestDownloadFileToDirectory:
@pytest.mark.unit
def test_delegates_to_download_single(self, loader, tmp_path):
loader._download_single_file = MagicMock(return_value=True)
assert loader._download_file_to_directory("f1", str(tmp_path)) is True
@pytest.mark.unit
def test_exception_returns_false(self, loader, tmp_path):
loader._download_single_file = MagicMock(side_effect=Exception("fail"))
# _download_file_to_directory calls _ensure_valid_token first, which may raise
# but the method wraps everything in try/except
assert loader._download_file_to_directory("f1", str(tmp_path)) is False
class TestDownloadFolderContents:
@pytest.mark.unit
def test_delegates_to_recursive(self, loader, tmp_path):
loader._download_folder_recursive = MagicMock(return_value=5)
count = loader._download_folder_contents("folder1", str(tmp_path))
assert count == 5
@pytest.mark.unit
def test_exception_returns_zero(self, loader, tmp_path):
loader.auth.is_token_expired.side_effect = Exception("fail")
count = loader._download_folder_contents("folder1", str(tmp_path))
assert count == 0
class TestRetryOnAuthFailureDecorator:
@pytest.mark.unit
def test_retries_on_401(self, loader):
"""The _retry_on_auth_failure decorator should retry after refreshing on 401."""
mock_resp_401 = MagicMock()
mock_resp_401.status_code = 401
http_error = real_requests.exceptions.HTTPError(response=mock_resp_401)
mock_resp_ok = MagicMock()
mock_resp_ok.content = b"ok"
mock_resp_ok.raise_for_status = MagicMock()
call_count = [0]
def get_side_effect(*args, **kwargs):
call_count[0] += 1
if call_count[0] == 1:
raise http_error
return mock_resp_ok
loader.auth.refresh_access_token.return_value = {"access_token": "new_at"}
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=get_side_effect):
content = loader._download_file_content("f1")
assert content == "ok"
assert loader.access_token == "new_at"
@pytest.mark.unit
def test_refresh_failure_raises_valueerror(self, loader):
"""If token refresh fails during retry, should raise ValueError."""
mock_resp_403 = MagicMock()
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")
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=http_error):
with pytest.raises(ValueError, match="could not be refreshed"):
loader._download_file_content("f1")
@pytest.mark.unit
def test_non_auth_http_error_not_retried(self, loader):
"""Non-401/403 HTTP errors should not trigger retry."""
mock_resp_500 = MagicMock()
mock_resp_500.status_code = 500
http_error = real_requests.exceptions.HTTPError(response=mock_resp_500)
with patch("application.parser.connectors.share_point.loader.requests.get", side_effect=http_error):
with pytest.raises(real_requests.exceptions.HTTPError):
loader._download_file_content("f1")