Files
uipath-explainator/tests/test_gemini.py
xiaomai 5ba2e3217a feat(report): restructure Gemini analysis and overview documentation
Update Gemini response schema to extract detailed overview, logic steps, data dependencies, and
consultation insights.
Add pipeline processing logic and file categorization explanations to OVERVIEW.md.
2026-04-02 10:33:19 +08:00

165 lines
6.3 KiB
Python

from __future__ import annotations
from pathlib import Path
from types import ModuleType, SimpleNamespace
import sys
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
fake_dotenv = ModuleType("dotenv")
fake_dotenv.load_dotenv = lambda *args, **kwargs: None
sys.modules.setdefault("dotenv", fake_dotenv)
from uipath_explainator.config import Settings
from uipath_explainator.gemini import GeminiAnalyzer
class FakeHttpOptions:
def __init__(self, **kwargs) -> None:
self.kwargs = kwargs
class FakeGenerateContentConfig:
def __init__(self, **kwargs) -> None:
self.kwargs = kwargs
class FakeClient:
def __init__(self, api_key: str, http_options: FakeHttpOptions) -> None:
self.api_key = api_key
self.http_options = http_options
self.models = SimpleNamespace()
class GeminiAnalyzerTests(unittest.TestCase):
def test_init_with_slots_declares_runtime_fields(self) -> None:
fake_types = SimpleNamespace(
HttpOptions=FakeHttpOptions,
GenerateContentConfig=FakeGenerateContentConfig,
)
fake_genai = ModuleType("google.genai")
fake_genai.Client = FakeClient
fake_genai.types = fake_types
fake_google = ModuleType("google")
fake_google.genai = fake_genai
with patch.dict(sys.modules, {"google": fake_google, "google.genai": fake_genai}):
analyzer = GeminiAnalyzer(Settings(api_key="test-key", base_url=None, model="gemini-test"))
self.assertIs(analyzer._types, fake_types)
self.assertIsInstance(analyzer._client, FakeClient)
self.assertEqual(analyzer._client.api_key, "test-key")
self.assertEqual(analyzer._client.http_options.kwargs, {"timeout": 120_000})
def test_init_strips_version_suffix_from_custom_base_url(self) -> None:
fake_types = SimpleNamespace(
HttpOptions=FakeHttpOptions,
GenerateContentConfig=FakeGenerateContentConfig,
)
fake_genai = ModuleType("google.genai")
fake_genai.Client = FakeClient
fake_genai.types = fake_types
fake_google = ModuleType("google")
fake_google.genai = fake_genai
with patch.dict(sys.modules, {"google": fake_google, "google.genai": fake_genai}):
analyzer = GeminiAnalyzer(
Settings(
api_key="test-key",
base_url="https://newapi.tootaio.com/v1beta/",
model="gemini-test",
)
)
self.assertEqual(
analyzer._client.http_options.kwargs,
{"base_url": "https://newapi.tootaio.com", "timeout": 120_000},
)
def test_markdown_output_uses_consulting_format(self) -> None:
fake_types = SimpleNamespace(
HttpOptions=FakeHttpOptions,
GenerateContentConfig=FakeGenerateContentConfig,
)
fake_genai = ModuleType("google.genai")
fake_genai.Client = FakeClient
fake_genai.types = fake_types
fake_google = ModuleType("google")
fake_google.genai = fake_genai
with patch.dict(sys.modules, {"google": fake_google, "google.genai": fake_genai}):
analyzer = GeminiAnalyzer(Settings(api_key="test-key", base_url=None, model="gemini-test"))
markdown = analyzer._to_markdown(
Path("Flows/Active.xaml"),
{
"overview": {
"purpose": "负责调用 VBA 并准备执行上下文",
"role": "主流程中的子流程节点",
"trigger": "由上游工作流通过 Invoke Workflow 调用",
},
"logic": {
"steps": [
{
"title": "读取 VBA 文件路径",
"detail": "从 `CodeFilePath` 读取脚本位置。",
"why": "定位需要执行的 VBA 代码。",
"result": "得到待执行的脚本文件。",
}
],
"decision_logic": ["如果 `CodeFilePath` 为空,则无法继续执行脚本。"],
"exceptions": ["当前文件未展示脚本执行失败后的补偿逻辑。"],
},
"data": {
"inputs": ["`Scripts/Keep.bas` 路径"],
"outputs": ["VBA 执行结果未直接在当前文件中落盘"],
"variables": ["`CodeFilePath`"],
"external_dependencies": ["外部 VBA 文件 `Scripts/Keep.bas`"],
},
"consultation": {
"business_meaning": "这是把业务动作下沉到 VBA 的桥接层。",
"risks": ["脚本文件缺失会导致执行失败。"],
"example": "例如:财务流程在这里调用 Excel VBA 完成批量格式整理。",
"unknowns": ["无法从当前文件确定 VBA 内部实现逻辑。"],
},
},
)
self.assertIn("## 文件定位", markdown)
self.assertIn("## 流程拆解", markdown)
self.assertIn("1. **读取 VBA 文件路径**", markdown)
self.assertIn("### 输入", markdown)
self.assertIn("## 咨询视角", markdown)
self.assertIn("## 场景范例", markdown)
def test_prompt_requires_strict_structured_json(self) -> None:
fake_types = SimpleNamespace(
HttpOptions=FakeHttpOptions,
GenerateContentConfig=FakeGenerateContentConfig,
)
fake_genai = ModuleType("google.genai")
fake_genai.Client = FakeClient
fake_genai.types = fake_types
fake_google = ModuleType("google")
fake_google.genai = fake_genai
with patch.dict(sys.modules, {"google": fake_google, "google.genai": fake_genai}):
analyzer = GeminiAnalyzer(Settings(api_key="test-key", base_url=None, model="gemini-test"))
prompt = analyzer._build_prompt(Path("main.xaml"), "<Sequence />")
self.assertIn("请严格返回 JSON", prompt)
self.assertIn("先讲这个文件在整个流程中的定位", prompt)
self.assertIn("判断逻辑、调用链、输入输出、关键变量、外部依赖", prompt)
if __name__ == "__main__":
unittest.main()