176 lines
6.1 KiB
Python
176 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
import json
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import openpyxl
|
|
from fastapi.testclient import TestClient
|
|
|
|
from backend.api import server as gui_server
|
|
from backend.api.server import AgentState, create_app
|
|
|
|
|
|
def _build_client(tmp: Path) -> tuple[TestClient, AgentState]:
|
|
state = AgentState(
|
|
cwd=tmp,
|
|
model='test-model',
|
|
base_url='http://127.0.0.1:8000/v1',
|
|
api_key='local-token',
|
|
allow_shell=False,
|
|
allow_write=False,
|
|
session_directory=tmp / 'sessions',
|
|
)
|
|
return TestClient(create_app(state)), state
|
|
|
|
|
|
class FeishuIntegrationTests(unittest.TestCase):
|
|
def test_convert_xlsx_and_docx_to_markdown(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
root = Path(tmp_dir)
|
|
xlsx_path = root / '样例.xlsx'
|
|
docx_path = root / '说明.docx'
|
|
_write_xlsx(xlsx_path)
|
|
_write_docx(docx_path)
|
|
|
|
xlsx_markdown = gui_server._convert_file_to_feishu_markdown(
|
|
xlsx_path,
|
|
title='表格样例',
|
|
)
|
|
docx_markdown = gui_server._convert_file_to_feishu_markdown(
|
|
docx_path,
|
|
title='文档样例',
|
|
)
|
|
|
|
self.assertIn('| query | label |', xlsx_markdown)
|
|
self.assertIn('| 怎么去公司 | Agent(tag="地图导航") |', xlsx_markdown)
|
|
self.assertIn('产品定义说明', docx_markdown)
|
|
self.assertIn('| 功能点 | 示例 |', docx_markdown)
|
|
|
|
def test_create_online_doc_requires_feishu_login(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
client, state = _build_client(Path(tmp_dir))
|
|
file_path = _write_account_file(state, 'alice', 'draft.md', '# hello')
|
|
|
|
with patch.object(
|
|
gui_server,
|
|
'_feishu_status_payload',
|
|
return_value={'logged_in': False, 'status': 'not_logged_in'},
|
|
):
|
|
response = client.post(
|
|
'/api/files/online-doc',
|
|
json={'account_id': 'alice', 'path': str(file_path)},
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 409)
|
|
self.assertEqual(response.json()['detail']['code'], 'feishu_not_logged_in')
|
|
|
|
def test_create_online_doc_calls_feishu_mcp(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
client, state = _build_client(Path(tmp_dir))
|
|
file_path = _write_account_file(state, 'alice', 'draft.md', '# hello')
|
|
|
|
with patch.object(
|
|
gui_server,
|
|
'_feishu_status_payload',
|
|
return_value={'logged_in': True, 'status': 'logged_in'},
|
|
), patch.object(
|
|
gui_server.MCPRuntime,
|
|
'call_tool',
|
|
return_value=(
|
|
'{"url":"https://mi.feishu.cn/docx/example"}',
|
|
{'server_name': 'feishu-mcp-pro', 'tool_name': 'doc_create'},
|
|
),
|
|
) as call_tool:
|
|
response = client.post(
|
|
'/api/files/online-doc',
|
|
json={'account_id': 'alice', 'path': str(file_path)},
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()
|
|
self.assertEqual(payload['url'], 'https://mi.feishu.cn/docx/example')
|
|
call_tool.assert_called_once()
|
|
self.assertEqual(call_tool.call_args.args[0], 'doc_create')
|
|
map_path = (
|
|
state.account_paths('alice')['base']
|
|
/ 'integrations'
|
|
/ 'feishu'
|
|
/ 'online-docs.json'
|
|
)
|
|
online_docs = json.loads(map_path.read_text(encoding='utf-8'))
|
|
self.assertEqual(
|
|
online_docs['files'][str(file_path)]['url'],
|
|
'https://mi.feishu.cn/docx/example',
|
|
)
|
|
|
|
def test_extract_first_url_ignores_wrapping_quotes(self) -> None:
|
|
url = gui_server._extract_first_url(
|
|
'{"url":"https://mi.feishu.cn/docx/CZIldpM3QofbbXxxAq1cEininTd"}'
|
|
)
|
|
|
|
self.assertEqual(url, 'https://mi.feishu.cn/docx/CZIldpM3QofbbXxxAq1cEininTd')
|
|
|
|
def test_create_online_doc_rejects_jsonl_for_now(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
client, state = _build_client(Path(tmp_dir))
|
|
file_path = _write_account_file(state, 'alice', 'records.jsonl', '{}\n')
|
|
|
|
response = client.post(
|
|
'/api/files/online-doc',
|
|
json={'account_id': 'alice', 'path': str(file_path)},
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertIn('json/jsonl', response.json()['detail'])
|
|
|
|
|
|
def _write_account_file(
|
|
state: AgentState,
|
|
account_id: str,
|
|
name: str,
|
|
content: str,
|
|
) -> Path:
|
|
path = state.account_paths(account_id)['sessions'] / 's1' / 'output' / name
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(content, encoding='utf-8')
|
|
return path
|
|
|
|
|
|
def _write_xlsx(path: Path) -> None:
|
|
workbook = openpyxl.Workbook()
|
|
sheet = workbook.active
|
|
sheet.title = 'Sheet1'
|
|
sheet.append(['query', 'label'])
|
|
sheet.append(['怎么去公司', 'Agent(tag="地图导航")'])
|
|
workbook.save(path)
|
|
|
|
|
|
def _write_docx(path: Path) -> None:
|
|
document_xml = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
|
<w:body>
|
|
<w:p><w:r><w:t>产品定义说明</w:t></w:r></w:p>
|
|
<w:tbl>
|
|
<w:tr>
|
|
<w:tc><w:p><w:r><w:t>功能点</w:t></w:r></w:p></w:tc>
|
|
<w:tc><w:p><w:r><w:t>示例</w:t></w:r></w:p></w:tc>
|
|
</w:tr>
|
|
<w:tr>
|
|
<w:tc><w:p><w:r><w:t>导航</w:t></w:r></w:p></w:tc>
|
|
<w:tc><w:p><w:r><w:t>导航到公司</w:t></w:r></w:p></w:tc>
|
|
</w:tr>
|
|
</w:tbl>
|
|
</w:body>
|
|
</w:document>
|
|
'''
|
|
with zipfile.ZipFile(path, 'w') as archive:
|
|
archive.writestr('word/document.xml', document_xml)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|