Files
arc53--docsgpt/tests/api/test_connector_routes.py
wehub-resource-sync fed8b2eed7
Backend release / release (push) Waiting to run
Bandit Security Scan / bandit_scan (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / manifest (push) Blocked by required conditions
Build and push DocsGPT FE Docker image for development / build (linux/amd64, ubuntu-latest, amd64) (push) Waiting to run
Build and push DocsGPT FE Docker image for development / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Waiting to run
Build and push DocsGPT FE Docker image for development / manifest (push) Blocked by required conditions
Python linting / ruff (push) Waiting to run
Run python tests with pytest / Run tests and count coverage (3.12) (push) Waiting to run
React Widget Build / build (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:28:29 +08:00

285 lines
7.5 KiB
Python

"""Tests for application/api/connector/routes.py"""
import base64
import json
import uuid
from unittest.mock import MagicMock, patch
import pytest
def _make_oid():
"""Return a unique 24-hex string usable as a document _id."""
return uuid.uuid4().hex[:24]
class _InMemoryCollection:
"""Minimal dict-backed Mongo collection for connector route tests."""
def __init__(self):
self._docs = []
def _matches(self, doc, query):
for k, v in query.items():
dv = doc.get(k)
# Normalize ObjectId vs string comparison
if str(dv) != str(v):
return False
return True
def insert_one(self, doc):
new_doc = dict(doc)
if "_id" not in new_doc:
new_doc["_id"] = _make_oid()
self._docs.append(new_doc)
result = MagicMock()
result.inserted_id = new_doc["_id"]
return result
def find_one(self, query):
import copy
for doc in self._docs:
if self._matches(doc, query):
return copy.deepcopy(doc)
return None
def find(self, query=None):
import copy
query = query or {}
return [copy.deepcopy(d) for d in self._docs if self._matches(d, query)]
def delete_one(self, query):
for i, doc in enumerate(self._docs):
if self._matches(doc, query):
self._docs.pop(i)
result = MagicMock()
result.deleted_count = 1
return result
result = MagicMock()
result.deleted_count = 0
return result
def update_one(self, query, update, upsert=False):
result = MagicMock()
result.modified_count = 0
for doc in self._docs:
if self._matches(doc, query):
if "$set" in update:
doc.update(update["$set"])
result.modified_count = 1
return result
result.modified_count = 0
return result
def find_one_and_update(self, query, update, upsert=False, return_document=False):
import copy
for doc in self._docs:
if self._matches(doc, query):
before = copy.deepcopy(doc)
if "$set" in update:
doc.update(update["$set"])
return before
if upsert:
new_doc = dict(query)
if "$set" in update:
new_doc.update(update["$set"])
self._docs.append(new_doc)
return None
@pytest.fixture
def app():
with patch("application.app.handle_auth", return_value={"sub": "test_user"}):
from application.app import app as flask_app
flask_app.config["TESTING"] = True
yield flask_app
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture
def mock_sessions(monkeypatch):
sessions = _InMemoryCollection()
sources = _InMemoryCollection()
monkeypatch.setattr("application.api.connector.routes.sessions_collection", sessions)
monkeypatch.setattr("application.api.connector.routes.sources_collection", sources)
return {"sessions": sessions, "sources": sources}
class TestConnectorAuth:
pass
@pytest.mark.unit
def test_missing_provider(self, client):
resp = client.get("/api/connectors/auth")
assert resp.status_code == 400
@pytest.mark.unit
def test_unsupported_provider(self, client):
resp = client.get("/api/connectors/auth?provider=dropbox")
assert resp.status_code == 400
@pytest.mark.unit
def test_unauthorized(self, client, app):
with patch("application.app.handle_auth", return_value=None):
resp = client.get("/api/connectors/auth?provider=google_drive")
data = json.loads(resp.data)
# decoded_token is None -> 401
assert resp.status_code == 401 or data.get("error") == "Unauthorized"
class TestConnectorFiles:
pass
@pytest.mark.unit
def test_missing_params(self, client):
resp = client.post("/api/connectors/files", json={"provider": "google_drive"})
assert resp.status_code == 400
class TestConnectorValidateSession:
pass
@pytest.mark.unit
def test_missing_params(self, client):
resp = client.post("/api/connectors/validate-session", json={"provider": "google_drive"})
assert resp.status_code == 400
class TestConnectorDisconnect:
pass
@pytest.mark.unit
def test_missing_provider(self, client):
resp = client.post("/api/connectors/disconnect", json={})
assert resp.status_code == 400
class TestConnectorSync:
pass
class TestConnectorCallbackStatus:
pass
@pytest.mark.unit
def test_success_status(self, client):
resp = client.get("/api/connectors/callback-status?status=success&message=OK&provider=google_drive&session_token=tok&user_email=u@e.com")
assert resp.status_code == 200
assert b"success" in resp.data
@pytest.mark.unit
def test_error_status(self, client):
resp = client.get("/api/connectors/callback-status?status=error&message=Failed")
assert resp.status_code == 200
assert b"error" in resp.data
@pytest.mark.unit
def test_cancelled_status(self, client):
resp = client.get("/api/connectors/callback-status?status=cancelled&message=Cancelled&provider=google_drive")
assert resp.status_code == 200
assert b"cancelled" in resp.data
@pytest.mark.unit
def test_unknown_status_defaults_to_error(self, client):
resp = client.get("/api/connectors/callback-status?status=badvalue")
assert resp.status_code == 200
assert b"error" in resp.data
@pytest.mark.unit
def test_html_escaping(self, client):
resp = client.get('/api/connectors/callback-status?status=error&message=<script>alert(1)</script>')
assert resp.status_code == 200
# The raw <script> tag should be escaped (not executable)
assert b"<script>alert(1)</script>" not in resp.data
class TestBuildCallbackRedirect:
pass
@pytest.mark.unit
def test_builds_url(self):
from application.api.connector.routes import build_callback_redirect
url = build_callback_redirect({"status": "success", "message": "OK"})
assert url.startswith("/api/connectors/callback-status?")
assert "status=success" in url
@pytest.mark.unit
class TestConnectorsCallback:
"""Tests for the ConnectorsCallback OAuth callback route."""
def _encode_state(self, state_dict):
return base64.urlsafe_b64encode(json.dumps(state_dict).encode()).decode()
def _patch_connector_creator(self):
"""Patch ConnectorCreator at both module-level and local-import locations."""
return patch(
"application.parser.connectors.connector_creator.ConnectorCreator",
)
@pytest.mark.unit
class TestConnectorFilesAdditional:
"""Additional tests for ConnectorFiles."""
@pytest.mark.unit
class TestConnectorFilesSearchQuery:
"""Test ConnectorFiles with search_query parameter."""
# ---------------------------------------------------------------------------
# Additional coverage tests
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestConnectorValidateSessionAdditional:
"""Cover uncovered branches in ConnectorValidateSession."""
@pytest.mark.unit
class TestConnectorDisconnectAdditional:
"""Cover uncovered branches in ConnectorDisconnect."""
@pytest.mark.unit
class TestConnectorSyncAdditional:
"""Cover uncovered branches in ConnectorSync."""