Files
wehub-resource-sync 4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

697 lines
26 KiB
Python

"""Unit tests for Google Docs integration client and tool."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock, patch
from integrations.google_docs.client import (
GoogleDocsClient,
GoogleDocsIntegrationConfig,
build_google_docs_client_from_env,
)
from integrations.google_docs.tools import create_google_docs_incident_report
class TestGoogleDocsIntegrationConfig:
"""Unit tests for GoogleDocsIntegrationConfig model."""
def test_valid_config(self) -> None:
"""Test valid config creation."""
config = GoogleDocsIntegrationConfig(
credentials_file="/path/to/credentials.json",
folder_id="folder123",
)
assert config.credentials_file == "/path/to/credentials.json"
assert config.folder_id == "folder123"
def test_config_with_integration_id(self) -> None:
"""Test config with integration_id."""
config = GoogleDocsIntegrationConfig(
credentials_file="/path/to/credentials.json",
folder_id="folder123",
integration_id="google-docs-1",
)
assert config.integration_id == "google-docs-1"
def test_config_normalizes_whitespace(self) -> None:
"""Test that credentials_file is normalized."""
config = GoogleDocsIntegrationConfig(
credentials_file=" /path/to/credentials.json ",
folder_id="folder123",
)
assert config.credentials_file == "/path/to/credentials.json"
class TestGoogleDocsClient:
"""Unit tests for GoogleDocsClient."""
def test_is_configured_when_file_exists(self, tmp_path: Path) -> None:
"""Test is_configured returns True when credentials file exists."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="folder123",
)
client = GoogleDocsClient(config)
assert client.is_configured is True
def test_is_configured_when_file_missing(self) -> None:
"""Test is_configured returns False when credentials file doesn't exist."""
config = GoogleDocsIntegrationConfig(
credentials_file="/nonexistent/path/credentials.json",
folder_id="folder123",
)
client = GoogleDocsClient(config)
assert client.is_configured is False
def test_is_configured_when_folder_id_missing(self, tmp_path: Path) -> None:
"""Test is_configured returns False when folder_id is empty."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="",
)
client = GoogleDocsClient(config)
assert client.is_configured is False
def test_create_document_no_folder_id(self) -> None:
"""Test create_document fails when no folder_id provided or configured."""
config = GoogleDocsIntegrationConfig(
credentials_file="/path/to/creds.json",
folder_id="",
)
client = GoogleDocsClient(config)
result = client.create_document("Test Report")
assert result["success"] is False
assert "No folder_id" in result["error"]
def test_create_document_success(self, tmp_path: Path) -> None:
"""Test successful document creation."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="folder123",
)
client = GoogleDocsClient(config)
# Mock the _get_services method
mock_drive_service = MagicMock()
mock_files = MagicMock()
mock_drive_service.files.return_value = mock_files
mock_create = MagicMock()
mock_files.create.return_value = mock_create
mock_create.execute.return_value = {"id": "doc123", "name": "Test Report"}
with patch.object(client, "_get_services", return_value=(None, mock_drive_service)):
result = client.create_document("Test Report")
assert result["success"] is True
assert result["document_id"] == "doc123"
assert result["title"] == "Test Report"
assert result["document_url"] == "https://docs.google.com/document/d/doc123/edit"
def test_create_document_api_error(self, tmp_path: Path) -> None:
"""Test document creation with API error."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="folder123",
)
client = GoogleDocsClient(config)
with patch.object(client, "_get_services", side_effect=Exception("API Error")):
result = client.create_document("Test Report")
assert result["success"] is False
# Error message now comes from _handle_google_api_error
assert "API Error" in result["error"]
def test_validate_access_success(self, tmp_path: Path) -> None:
"""Test successful validation."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="folder123",
)
client = GoogleDocsClient(config)
# Mock the Drive service
mock_drive_service = MagicMock()
mock_files = MagicMock()
mock_drive_service.files.return_value = mock_files
# Mock for _validate_folder_exists (get folder info)
mock_get = MagicMock()
mock_get.execute.return_value = {
"id": "folder123",
"name": "Incident Reports",
"mimeType": "application/vnd.google-apps.folder",
}
mock_files.get.return_value = mock_get
# Mock for listing folder contents
mock_list = MagicMock()
mock_files.list.return_value = mock_list
mock_list.execute.return_value = {
"files": [
{"id": "1", "name": "file1", "mimeType": "application/pdf"},
{"id": "2", "name": "file2", "mimeType": "application/vnd.google-apps.document"},
]
}
with patch.object(client, "_get_services", return_value=(None, mock_drive_service)):
result = client.validate_access()
assert result["success"] is True
assert result["folder_id"] == "folder123"
assert result["folder_name"] == "Incident Reports"
assert result["file_count"] == 2
def test_probe_access_success(self, tmp_path: Path) -> None:
"""Test probe_access returns a passed probe result for a valid folder."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="folder123",
)
client = GoogleDocsClient(config)
with patch.object(
client, "validate_access", return_value={"success": True, "file_count": 2}
):
result = client.probe_access()
assert result.status == "passed"
assert "folder123" in result.detail
assert "2 items" in result.detail
def test_share_document_success(self, tmp_path: Path) -> None:
"""Test successful document sharing."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="folder123",
)
client = GoogleDocsClient(config)
# Mock the Drive service
mock_drive_service = MagicMock()
mock_permissions = MagicMock()
mock_drive_service.permissions.return_value = mock_permissions
mock_create = MagicMock()
mock_permissions.create.return_value = mock_create
mock_create.execute.return_value = {"id": "perm123"}
with patch.object(client, "_get_services", return_value=(None, mock_drive_service)):
result = client.share_document("doc123", "user@example.com", role="writer")
assert result["success"] is True
def test_get_document_success(self, tmp_path: Path) -> None:
"""Test successful document retrieval."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="folder123",
)
client = GoogleDocsClient(config)
# Mock the Docs service
mock_docs_service = MagicMock()
mock_documents = MagicMock()
mock_docs_service.documents.return_value = mock_documents
mock_get = MagicMock()
mock_documents.get.return_value = mock_get
mock_get.execute.return_value = {
"documentId": "doc123",
"title": "Test Document",
"body": {"content": []},
}
with patch.object(client, "_get_services", return_value=(mock_docs_service, None)):
result = client.get_document("doc123")
assert result["success"] is True
assert result["document_id"] == "doc123"
assert result["title"] == "Test Document"
class TestBuildGoogleDocsClientFromEnv:
"""Unit tests for build_google_docs_client_from_env function."""
@patch.dict("os.environ", {}, clear=True)
def test_returns_none_when_env_vars_missing(self) -> None:
"""Test returns None when environment variables are not set."""
result = build_google_docs_client_from_env()
assert result is None
@patch.dict(
"os.environ",
{
"GOOGLE_CREDENTIALS_FILE": "/path/to/creds.json",
"GOOGLE_DRIVE_FOLDER_ID": "folder123",
},
clear=True,
)
def test_returns_client_when_env_vars_set(self) -> None:
"""Test returns client when environment variables are set."""
result = build_google_docs_client_from_env()
assert result is not None
assert isinstance(result, GoogleDocsClient)
assert result.config.credentials_file == "/path/to/creds.json"
assert result.config.folder_id == "folder123"
class TestGoogleDocsIncidentReportTool:
"""Unit tests for create_google_docs_incident_report tool."""
@patch("integrations.google_docs.tools.GoogleDocsClient")
def test_create_report_success(
self,
mock_client_class: MagicMock,
) -> None:
"""Test successful incident report creation."""
mock_client = MagicMock()
mock_client.is_configured = True
mock_client.create_incident_report.return_value = {
"success": True,
"document_id": "doc123",
"document_url": "https://docs.google.com/document/d/doc123/edit",
"title": "Incident Report: Test",
}
mock_client_class.return_value = mock_client
result = create_google_docs_incident_report(
title="Incident Report: Test",
summary="Test incident summary",
root_cause="Test root cause",
severity="high",
credentials_file="/path/to/creds.json",
folder_id="folder123",
)
assert result["success"] is True
assert result["document_id"] == "doc123"
assert result["document_url"] == "https://docs.google.com/document/d/doc123/edit"
@patch("integrations.google_docs.tools.GoogleDocsClient")
def test_create_report_not_configured(
self,
mock_client_class: MagicMock,
) -> None:
"""Test report creation when client is not configured."""
mock_client = MagicMock()
mock_client.is_configured = False
mock_client_class.return_value = mock_client
result = create_google_docs_incident_report(
title="Incident Report: Test",
summary="Test incident summary",
root_cause="Test root cause",
severity="high",
credentials_file="/path/to/creds.json",
folder_id="folder123",
)
assert result["success"] is False
assert "not properly configured" in result["error"]
@patch("integrations.google_docs.tools.GoogleDocsClient")
def test_create_report_with_all_fields(
self,
mock_client_class: MagicMock,
) -> None:
"""Test report creation with all optional fields."""
mock_client = MagicMock()
mock_client.is_configured = True
mock_client.create_incident_report.return_value = {
"success": True,
"document_id": "doc123",
"document_url": "https://docs.google.com/document/d/doc123/edit",
"title": "Incident Report: Test",
}
mock_client_class.return_value = mock_client
evidence = [{"title": "Log entry", "description": "Error in logs"}]
timeline = [{"time": "10:00", "description": "Alert fired"}]
remediation_steps = ["Step 1", "Step 2"]
follow_up_actions = ["Action 1", "Action 2"]
result = create_google_docs_incident_report(
title="Incident Report: Test",
summary="Test incident summary",
root_cause="Test root cause",
severity="critical",
credentials_file="/path/to/creds.json",
folder_id="folder123",
evidence=evidence,
timeline=timeline,
remediation_steps=remediation_steps,
follow_up_actions=follow_up_actions,
share_with=["user@example.com"],
)
assert result["success"] is True
# Verify share_document was called
mock_client.share_document.assert_called_once_with(
"doc123", "user@example.com", role="writer"
)
@patch("integrations.google_docs.tools.GoogleDocsClient")
def test_create_report_api_error(
self,
mock_client_class: MagicMock,
) -> None:
"""Test report creation when API returns error."""
mock_client = MagicMock()
mock_client.is_configured = True
mock_client.create_incident_report.return_value = {
"success": False,
"error": "API Error",
}
mock_client_class.return_value = mock_client
result = create_google_docs_incident_report(
title="Incident Report: Test",
summary="Test incident summary",
root_cause="Test root cause",
severity="high",
credentials_file="/path/to/creds.json",
folder_id="folder123",
)
assert result["success"] is False
assert result["error"] == "API Error"
def test_tool_is_available_when_configured(self) -> None:
"""Test that is_available returns True when google_docs is configured."""
from integrations.google_docs.tools import _is_available
sources = {"google_docs": {"configured": True}}
assert _is_available(sources) is True
def test_tool_is_available_when_not_configured(self) -> None:
"""Test that is_available returns False when google_docs is not configured."""
from integrations.google_docs.tools import _is_available
sources = {}
assert _is_available(sources) is False
def test_tool_extract_params(self) -> None:
"""Test that extract_params returns correct parameters."""
from integrations.google_docs.tools import _extract_params
sources = {
"google_docs": {
"credentials_file": "/path/to/creds.json",
"folder_id": "folder123",
}
}
params = _extract_params(sources)
assert params["credentials_file"] == "/path/to/creds.json"
assert params["folder_id"] == "folder123"
@patch("integrations.google_docs.tools.GoogleDocsClient")
def test_create_report_with_custom_share_role(
self,
mock_client_class: MagicMock,
) -> None:
"""Test report creation with custom share_role parameter."""
mock_client = MagicMock()
mock_client.is_configured = True
mock_client.create_incident_report.return_value = {
"success": True,
"document_id": "doc123",
"document_url": "https://docs.google.com/document/d/doc123/edit",
"title": "Incident Report: Test",
}
mock_client_class.return_value = mock_client
result = create_google_docs_incident_report(
title="Incident Report: Test",
summary="Test incident summary",
root_cause="Test root cause",
severity="high",
credentials_file="/path/to/creds.json",
folder_id="folder123",
share_with=["user@example.com"],
share_role="reader",
)
assert result["success"] is True
# Verify share_document was called with reader role
mock_client.share_document.assert_called_once_with(
"doc123", "user@example.com", role="reader"
)
@patch("integrations.google_docs.tools.GoogleDocsClient")
def test_create_report_with_invalid_share_role_defaults_to_writer(
self,
mock_client_class: MagicMock,
) -> None:
"""Test that invalid share_role defaults to writer."""
mock_client = MagicMock()
mock_client.is_configured = True
mock_client.create_incident_report.return_value = {
"success": True,
"document_id": "doc123",
"document_url": "https://docs.google.com/document/d/doc123/edit",
"title": "Incident Report: Test",
}
mock_client_class.return_value = mock_client
result = create_google_docs_incident_report(
title="Incident Report: Test",
summary="Test incident summary",
root_cause="Test root cause",
severity="high",
credentials_file="/path/to/creds.json",
folder_id="folder123",
share_with=["user@example.com"],
share_role="invalid_role",
)
assert result["success"] is True
# Verify share_document was called with writer role (default)
mock_client.share_document.assert_called_once_with(
"doc123", "user@example.com", role="writer"
)
class TestGoogleDocsIntegrationConfigTimeout:
"""Unit tests for GoogleDocsIntegrationConfig timeout validation."""
def test_default_timeout(self) -> None:
"""Test default timeout is 30 seconds."""
config = GoogleDocsIntegrationConfig(
credentials_file="/path/to/creds.json",
folder_id="folder123",
)
assert config.timeout_seconds == 30
def test_custom_timeout(self) -> None:
"""Test custom timeout can be set."""
config = GoogleDocsIntegrationConfig(
credentials_file="/path/to/creds.json",
folder_id="folder123",
timeout_seconds=60,
)
assert config.timeout_seconds == 60
def test_timeout_minimum_bound(self) -> None:
"""Test timeout has minimum of 5 seconds."""
config = GoogleDocsIntegrationConfig(
credentials_file="/path/to/creds.json",
folder_id="folder123",
timeout_seconds=1, # Should be clamped to 5
)
assert config.timeout_seconds == 5
def test_timeout_maximum_bound(self) -> None:
"""Test timeout has maximum of 300 seconds."""
config = GoogleDocsIntegrationConfig(
credentials_file="/path/to/creds.json",
folder_id="folder123",
timeout_seconds=600, # Should be clamped to 300
)
assert config.timeout_seconds == 300
def test_timeout_invalid_value_defaults_to_30(self) -> None:
"""Test invalid timeout value defaults to 30."""
config = GoogleDocsIntegrationConfig(
credentials_file="/path/to/creds.json",
folder_id="folder123",
timeout_seconds="invalid", # type: ignore[arg-type]
)
assert config.timeout_seconds == 30
class TestGoogleDocsClientIncidentReport:
"""Unit tests for create_incident_report method."""
@patch.object(GoogleDocsClient, "_validate_folder_exists")
@patch.object(GoogleDocsClient, "create_document")
@patch.object(GoogleDocsClient, "insert_content")
def test_create_incident_report_with_all_params(
self,
mock_insert: MagicMock,
mock_create: MagicMock,
mock_validate: MagicMock,
tmp_path: Path,
) -> None:
"""Test create_incident_report with all parameters."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
mock_validate.return_value = {"success": True, "folder_name": "Test Folder"}
mock_create.return_value = {
"success": True,
"document_id": "doc123",
"document_url": "https://docs.google.com/document/d/doc123/edit",
}
mock_insert.return_value = {"success": True}
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="folder123",
)
client = GoogleDocsClient(config)
evidence = [
{"title": "Log Error", "description": "Database connection failed"},
{"title": "Metric Spike", "description": "CPU usage at 100%"},
]
timeline = [
{"time": "10:00 UTC", "description": "Alert triggered"},
{"time": "10:15 UTC", "description": "Investigation started"},
{"time": "11:30 UTC", "description": "Issue resolved"},
]
remediation_steps = ["Restarted database", "Scaled up instances"]
follow_up_actions = ["Add monitoring", "Review capacity planning"]
result = client.create_incident_report(
title="Incident: Database Outage",
summary="Database connection pool exhausted causing service degradation",
root_cause="Connection leak in application code",
evidence=evidence,
timeline=timeline,
severity="critical",
remediation_steps=remediation_steps,
follow_up_actions=follow_up_actions,
)
assert result["success"] is True
assert result["document_id"] == "doc123"
mock_validate.assert_called_once()
mock_create.assert_called_once_with("Incident: Database Outage")
mock_insert.assert_called_once()
@patch.object(GoogleDocsClient, "_validate_folder_exists")
def test_create_incident_report_folder_not_found(
self,
mock_validate: MagicMock,
tmp_path: Path,
) -> None:
"""Test create_incident_report when folder doesn't exist."""
creds_file = tmp_path / "credentials.json"
creds_file.write_text('{"test": "content"}')
mock_validate.return_value = {
"success": False,
"error": "Folder not found",
}
config = GoogleDocsIntegrationConfig(
credentials_file=str(creds_file),
folder_id="invalid_folder",
)
client = GoogleDocsClient(config)
result = client.create_incident_report(
title="Test Report",
summary="Test summary",
root_cause="Test cause",
evidence=[],
timeline=[],
severity="high",
)
assert result["success"] is False
assert "Folder not found" in result["error"]
class TestGoogleDocsClientErrorHandling:
"""Unit tests for Google API error handling."""
def test_handle_google_api_error_with_403(self) -> None:
"""Test handling of 403 permission error."""
from integrations.google_docs.client import _handle_google_api_error
class MockHttpError(Exception):
def __init__(self) -> None:
super().__init__()
self.resp = type("obj", (object,), {"status": 403})()
exc = MockHttpError()
result = _handle_google_api_error(exc, "creating document")
assert result["success"] is False
assert "Insufficient permissions" in result["error"]
def test_handle_google_api_error_with_404(self) -> None:
"""Test handling of 404 not found error."""
from integrations.google_docs.client import _handle_google_api_error
class MockHttpError(Exception):
def __init__(self) -> None:
super().__init__()
self.resp = type("obj", (object,), {"status": 404})()
exc = MockHttpError()
result = _handle_google_api_error(exc, "accessing folder")
assert result["success"] is False
assert "Resource not found" in result["error"]
def test_handle_google_api_error_with_429(self) -> None:
"""Test handling of 429 rate limit error."""
from integrations.google_docs.client import _handle_google_api_error
class MockHttpError(Exception):
def __init__(self) -> None:
super().__init__()
self.resp = type("obj", (object,), {"status": 429})()
exc = MockHttpError()
result = _handle_google_api_error(exc, "inserting content")
assert result["success"] is False
assert "Rate limit exceeded" in result["error"]
def test_handle_google_api_error_generic(self) -> None:
"""Test handling of generic error."""
from integrations.google_docs.client import _handle_google_api_error
exc = Exception("Something went wrong")
result = _handle_google_api_error(exc, "sharing document")
assert result["success"] is False
assert "Something went wrong" in result["error"]