mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-12 01:02:39 +02:00
test(e2e): add Dropbox backend fake
This commit is contained in:
parent
2d78dda487
commit
69437b1fe4
4 changed files with 216 additions and 0 deletions
179
surfsense_backend/tests/e2e/fakes/dropbox_api.py
Normal file
179
surfsense_backend/tests/e2e/fakes/dropbox_api.py
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
"""Strict Dropbox HTTP/API fakes for Playwright E2E.
|
||||||
|
|
||||||
|
This module patches the Dropbox OAuth route and indexer consumer-site
|
||||||
|
bindings. It keeps the production add/callback/indexing flow intact while
|
||||||
|
serving deterministic Dropbox-shaped token, profile, metadata, and file
|
||||||
|
content responses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
_DROPBOX_FIXTURE_PATH = Path(__file__).parent / "fixtures" / "dropbox_files.json"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_dropbox_fixture() -> dict[str, Any]:
|
||||||
|
with _DROPBOX_FIXTURE_PATH.open() as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
_DROPBOX_FIXTURE = _load_dropbox_fixture()
|
||||||
|
|
||||||
|
|
||||||
|
class _StrictFakeMixin:
|
||||||
|
_component_name: str = "<unknown>"
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> Any:
|
||||||
|
raise NotImplementedError(
|
||||||
|
f"E2E Dropbox fake missing surface: "
|
||||||
|
f"{self._component_name}.{name!r}. Add it to "
|
||||||
|
f"surfsense_backend/tests/e2e/fakes/dropbox_api.py."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeDropboxClient(_StrictFakeMixin):
|
||||||
|
_component_name = "DropboxClient"
|
||||||
|
|
||||||
|
def __init__(self, session: Any, connector_id: int):
|
||||||
|
self._session = session
|
||||||
|
self._connector_id = connector_id
|
||||||
|
|
||||||
|
async def _get_valid_token(self) -> str:
|
||||||
|
return "fake-dropbox-access-token"
|
||||||
|
|
||||||
|
async def list_folder(
|
||||||
|
self, path: str = ""
|
||||||
|
) -> tuple[list[dict[str, Any]], str | None]:
|
||||||
|
items = _DROPBOX_FIXTURE.get(path)
|
||||||
|
if not isinstance(items, list):
|
||||||
|
return [], f"E2E Dropbox fake has no folder for path={path!r}."
|
||||||
|
return [dict(item) for item in items], None
|
||||||
|
|
||||||
|
async def get_latest_cursor(self, path: str = "") -> tuple[str | None, str | None]:
|
||||||
|
if path not in _DROPBOX_FIXTURE:
|
||||||
|
return None, f"E2E Dropbox fake has no cursor for path={path!r}."
|
||||||
|
return f"fake-dropbox-cursor:{path or 'root'}", None
|
||||||
|
|
||||||
|
async def get_changes(
|
||||||
|
self, cursor: str
|
||||||
|
) -> tuple[list[dict[str, Any]], str | None, str | None]:
|
||||||
|
return [], cursor, None
|
||||||
|
|
||||||
|
async def get_metadata(self, path: str) -> tuple[dict[str, Any] | None, str | None]:
|
||||||
|
metadata = _dropbox_get_metadata(path)
|
||||||
|
if metadata is None:
|
||||||
|
return None, f"E2E Dropbox fake has no metadata for path={path!r}."
|
||||||
|
return metadata, None
|
||||||
|
|
||||||
|
async def download_file(self, path: str) -> tuple[bytes | None, str | None]:
|
||||||
|
content = _DROPBOX_FIXTURE.get("_file_contents", {}).get(path)
|
||||||
|
if content is None:
|
||||||
|
return None, f"E2E Dropbox fake has no content for path={path!r}."
|
||||||
|
return content.encode("utf-8"), None
|
||||||
|
|
||||||
|
async def download_file_to_disk(self, path: str, dest_path: str) -> str | None:
|
||||||
|
content = _DROPBOX_FIXTURE.get("_file_contents", {}).get(path)
|
||||||
|
if content is None:
|
||||||
|
return f"E2E Dropbox fake has no content for path={path!r}."
|
||||||
|
with open(dest_path, "wb") as f:
|
||||||
|
f.write(content.encode("utf-8"))
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_current_account(self) -> tuple[dict[str, Any] | None, str | None]:
|
||||||
|
return _dropbox_current_account(), None
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeAsyncClient(_StrictFakeMixin):
|
||||||
|
_component_name = "httpx.AsyncClient"
|
||||||
|
|
||||||
|
def __init__(self, *args: Any, **kwargs: Any):
|
||||||
|
del args, kwargs
|
||||||
|
|
||||||
|
async def __aenter__(self) -> _FakeAsyncClient:
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
||||||
|
del exc_type, exc, tb
|
||||||
|
|
||||||
|
async def post(self, url: str, *args: Any, **kwargs: Any) -> httpx.Response:
|
||||||
|
del args, kwargs
|
||||||
|
if url == "https://api.dropboxapi.com/oauth2/token":
|
||||||
|
return _json_response(
|
||||||
|
"POST",
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
"access_token": "fake-dropbox-access-token",
|
||||||
|
"refresh_token": "fake-dropbox-refresh-token",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600,
|
||||||
|
"account_id": "dbid:fake-dropbox-account",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if url == "https://api.dropboxapi.com/2/users/get_current_account":
|
||||||
|
return _json_response("POST", url, _dropbox_current_account())
|
||||||
|
raise NotImplementedError(f"E2E Dropbox fake unexpected POST URL: {url!r}")
|
||||||
|
|
||||||
|
async def request(
|
||||||
|
self, method: str, url: str, *args: Any, **kwargs: Any
|
||||||
|
) -> httpx.Response:
|
||||||
|
del args, kwargs
|
||||||
|
raise NotImplementedError(
|
||||||
|
f"E2E Dropbox fake unexpected request: {method!r} {url!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeHttpxModule(_StrictFakeMixin):
|
||||||
|
_component_name = "httpx"
|
||||||
|
|
||||||
|
AsyncClient = _FakeAsyncClient
|
||||||
|
|
||||||
|
|
||||||
|
def _json_response(
|
||||||
|
method: str, url: str, payload: dict[str, Any], status_code: int = 200
|
||||||
|
) -> httpx.Response:
|
||||||
|
return httpx.Response(
|
||||||
|
status_code=status_code,
|
||||||
|
json=payload,
|
||||||
|
request=httpx.Request(method, url),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _dropbox_current_account() -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"email": "dropbox-e2e@surfsense.example",
|
||||||
|
"name": {"display_name": "SurfSense Dropbox E2E"},
|
||||||
|
"account_id": "dbid:fake-dropbox-account",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _dropbox_get_metadata(path: str | None) -> dict[str, Any] | None:
|
||||||
|
for items in _DROPBOX_FIXTURE.values():
|
||||||
|
if not isinstance(items, list):
|
||||||
|
continue
|
||||||
|
for entry in items:
|
||||||
|
if entry.get("path_lower") == path or entry.get("id") == path:
|
||||||
|
return dict(entry)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def install(active_patches: list[Any]) -> None:
|
||||||
|
"""Patch production Dropbox bindings to use strict Dropbox fakes."""
|
||||||
|
targets = [
|
||||||
|
("app.routes.dropbox_add_connector_route.httpx", _FakeHttpxModule()),
|
||||||
|
("app.routes.dropbox_add_connector_route.DropboxClient", _FakeDropboxClient),
|
||||||
|
(
|
||||||
|
"app.tasks.connector_indexers.dropbox_indexer.DropboxClient",
|
||||||
|
_FakeDropboxClient,
|
||||||
|
),
|
||||||
|
("app.connectors.dropbox.client.httpx", _FakeHttpxModule()),
|
||||||
|
]
|
||||||
|
for target, replacement in targets:
|
||||||
|
p = patch(target, replacement)
|
||||||
|
p.start()
|
||||||
|
active_patches.append(p)
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"": [
|
||||||
|
{
|
||||||
|
".tag": "file",
|
||||||
|
"id": "id:fake-dropbox-canary",
|
||||||
|
"name": "e2e-dropbox-canary.txt",
|
||||||
|
"path_lower": "/e2e-dropbox-canary.txt",
|
||||||
|
"path_display": "/e2e-dropbox-canary.txt",
|
||||||
|
"size": 152,
|
||||||
|
"is_downloadable": true,
|
||||||
|
"server_modified": "2026-05-08T00:00:00Z",
|
||||||
|
"client_modified": "2026-05-08T00:00:00Z",
|
||||||
|
"content_hash": "fake-dropbox-hash-001"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_file_contents": {
|
||||||
|
"/e2e-dropbox-canary.txt": "Canary token for Dropbox E2E tests: SURFSENSE_E2E_CANARY_TOKEN_DROPBOX_001\nThis file proves the Dropbox indexing pipeline ran end-to-end."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -75,6 +75,12 @@ os.environ.setdefault(
|
||||||
"ONEDRIVE_REDIRECT_URI",
|
"ONEDRIVE_REDIRECT_URI",
|
||||||
"http://localhost:8000/api/v1/auth/onedrive/connector/callback",
|
"http://localhost:8000/api/v1/auth/onedrive/connector/callback",
|
||||||
)
|
)
|
||||||
|
os.environ.setdefault("DROPBOX_APP_KEY", "fake-dropbox-app-key")
|
||||||
|
os.environ.setdefault("DROPBOX_APP_SECRET", "fake-dropbox-app-secret")
|
||||||
|
os.environ.setdefault(
|
||||||
|
"DROPBOX_REDIRECT_URI",
|
||||||
|
"http://localhost:8000/api/v1/auth/dropbox/connector/callback",
|
||||||
|
)
|
||||||
os.environ["SLACK_CLIENT_ID"] = "fake-slack-mcp-client-id"
|
os.environ["SLACK_CLIENT_ID"] = "fake-slack-mcp-client-id"
|
||||||
os.environ["SLACK_CLIENT_SECRET"] = "fake-slack-mcp-client-secret"
|
os.environ["SLACK_CLIENT_SECRET"] = "fake-slack-mcp-client-secret"
|
||||||
|
|
||||||
|
|
@ -105,6 +111,7 @@ from app.app import app # noqa: E402
|
||||||
from tests.e2e.fakes import ( # noqa: E402
|
from tests.e2e.fakes import ( # noqa: E402
|
||||||
confluence_indexer as _fake_confluence_indexer,
|
confluence_indexer as _fake_confluence_indexer,
|
||||||
confluence_oauth as _fake_confluence_oauth,
|
confluence_oauth as _fake_confluence_oauth,
|
||||||
|
dropbox_api as _fake_dropbox_api,
|
||||||
embeddings as _fake_embeddings,
|
embeddings as _fake_embeddings,
|
||||||
jira_module as _fake_jira_module,
|
jira_module as _fake_jira_module,
|
||||||
linear_module as _fake_linear_module,
|
linear_module as _fake_linear_module,
|
||||||
|
|
@ -133,6 +140,7 @@ def _patch_llm_bindings() -> None:
|
||||||
"app.tasks.connector_indexers.google_gmail_indexer.get_user_long_context_llm",
|
"app.tasks.connector_indexers.google_gmail_indexer.get_user_long_context_llm",
|
||||||
"app.tasks.connector_indexers.notion_indexer.get_user_long_context_llm",
|
"app.tasks.connector_indexers.notion_indexer.get_user_long_context_llm",
|
||||||
"app.tasks.connector_indexers.onedrive_indexer.get_user_long_context_llm",
|
"app.tasks.connector_indexers.onedrive_indexer.get_user_long_context_llm",
|
||||||
|
"app.tasks.connector_indexers.dropbox_indexer.get_user_long_context_llm",
|
||||||
"app.tasks.connector_indexers.local_folder_indexer.get_user_long_context_llm",
|
"app.tasks.connector_indexers.local_folder_indexer.get_user_long_context_llm",
|
||||||
"app.tasks.document_processors._save.get_user_long_context_llm",
|
"app.tasks.document_processors._save.get_user_long_context_llm",
|
||||||
"app.tasks.document_processors.markdown_processor.get_user_long_context_llm",
|
"app.tasks.document_processors.markdown_processor.get_user_long_context_llm",
|
||||||
|
|
@ -188,6 +196,7 @@ _fake_confluence_oauth.install(_active_patches)
|
||||||
_fake_confluence_indexer.install(_active_patches)
|
_fake_confluence_indexer.install(_active_patches)
|
||||||
_fake_native_google.install(_active_patches)
|
_fake_native_google.install(_active_patches)
|
||||||
_fake_onedrive_graph.install(_active_patches)
|
_fake_onedrive_graph.install(_active_patches)
|
||||||
|
_fake_dropbox_api.install(_active_patches)
|
||||||
_fake_notion_module.install(_active_patches)
|
_fake_notion_module.install(_active_patches)
|
||||||
_fake_linear_module.install(_active_patches)
|
_fake_linear_module.install(_active_patches)
|
||||||
_fake_jira_module.install(_active_patches)
|
_fake_jira_module.install(_active_patches)
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,12 @@ os.environ.setdefault(
|
||||||
"ONEDRIVE_REDIRECT_URI",
|
"ONEDRIVE_REDIRECT_URI",
|
||||||
"http://localhost:8000/api/v1/auth/onedrive/connector/callback",
|
"http://localhost:8000/api/v1/auth/onedrive/connector/callback",
|
||||||
)
|
)
|
||||||
|
os.environ.setdefault("DROPBOX_APP_KEY", "fake-dropbox-app-key")
|
||||||
|
os.environ.setdefault("DROPBOX_APP_SECRET", "fake-dropbox-app-secret")
|
||||||
|
os.environ.setdefault(
|
||||||
|
"DROPBOX_REDIRECT_URI",
|
||||||
|
"http://localhost:8000/api/v1/auth/dropbox/connector/callback",
|
||||||
|
)
|
||||||
os.environ["SLACK_CLIENT_ID"] = "fake-slack-mcp-client-id"
|
os.environ["SLACK_CLIENT_ID"] = "fake-slack-mcp-client-id"
|
||||||
os.environ["SLACK_CLIENT_SECRET"] = "fake-slack-mcp-client-secret"
|
os.environ["SLACK_CLIENT_SECRET"] = "fake-slack-mcp-client-secret"
|
||||||
|
|
||||||
|
|
@ -90,6 +96,7 @@ from app.celery_app import celery_app # noqa: E402
|
||||||
from tests.e2e.fakes import ( # noqa: E402
|
from tests.e2e.fakes import ( # noqa: E402
|
||||||
confluence_indexer as _fake_confluence_indexer,
|
confluence_indexer as _fake_confluence_indexer,
|
||||||
confluence_oauth as _fake_confluence_oauth,
|
confluence_oauth as _fake_confluence_oauth,
|
||||||
|
dropbox_api as _fake_dropbox_api,
|
||||||
embeddings as _fake_embeddings,
|
embeddings as _fake_embeddings,
|
||||||
jira_module as _fake_jira_module,
|
jira_module as _fake_jira_module,
|
||||||
linear_module as _fake_linear_module,
|
linear_module as _fake_linear_module,
|
||||||
|
|
@ -117,6 +124,7 @@ def _patch_llm_bindings() -> None:
|
||||||
"app.tasks.connector_indexers.google_gmail_indexer.get_user_long_context_llm",
|
"app.tasks.connector_indexers.google_gmail_indexer.get_user_long_context_llm",
|
||||||
"app.tasks.connector_indexers.notion_indexer.get_user_long_context_llm",
|
"app.tasks.connector_indexers.notion_indexer.get_user_long_context_llm",
|
||||||
"app.tasks.connector_indexers.onedrive_indexer.get_user_long_context_llm",
|
"app.tasks.connector_indexers.onedrive_indexer.get_user_long_context_llm",
|
||||||
|
"app.tasks.connector_indexers.dropbox_indexer.get_user_long_context_llm",
|
||||||
"app.tasks.connector_indexers.local_folder_indexer.get_user_long_context_llm",
|
"app.tasks.connector_indexers.local_folder_indexer.get_user_long_context_llm",
|
||||||
"app.tasks.document_processors._save.get_user_long_context_llm",
|
"app.tasks.document_processors._save.get_user_long_context_llm",
|
||||||
"app.tasks.document_processors.markdown_processor.get_user_long_context_llm",
|
"app.tasks.document_processors.markdown_processor.get_user_long_context_llm",
|
||||||
|
|
@ -172,6 +180,7 @@ _fake_confluence_oauth.install(_active_patches)
|
||||||
_fake_confluence_indexer.install(_active_patches)
|
_fake_confluence_indexer.install(_active_patches)
|
||||||
_fake_native_google.install(_active_patches)
|
_fake_native_google.install(_active_patches)
|
||||||
_fake_onedrive_graph.install(_active_patches)
|
_fake_onedrive_graph.install(_active_patches)
|
||||||
|
_fake_dropbox_api.install(_active_patches)
|
||||||
_fake_notion_module.install(_active_patches)
|
_fake_notion_module.install(_active_patches)
|
||||||
_fake_linear_module.install(_active_patches)
|
_fake_linear_module.install(_active_patches)
|
||||||
_fake_jira_module.install(_active_patches)
|
_fake_jira_module.install(_active_patches)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue