Initial commit: 知识库管理器应用
- 实现基于 PySide6 的 GUI 界面 - 集成 FastAPI 知识库服务器 API - 支持查看、编辑、提交 Markdown 文件 - 包含完整的 pytest-qt 测试套件 - 添加功能列表文档
This commit is contained in:
80
FEATURES.md
Normal file
80
FEATURES.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# 功能列表
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 服务器管理
|
||||
- ✅ 服务器地址配置
|
||||
- ✅ 连接测试
|
||||
- ✅ 状态显示
|
||||
- ✅ 默认地址预设(http://43.134.1.17:8800/)
|
||||
|
||||
### 2. 文件管理
|
||||
- ✅ 获取服务器文件列表
|
||||
- ✅ 自动过滤只显示 .md 文件
|
||||
- ✅ 刷新文件列表
|
||||
- ✅ 文件列表点击加载
|
||||
|
||||
### 3. 文件编辑
|
||||
- ✅ 查看 Markdown 文件内容
|
||||
- ✅ 在线编辑文件内容
|
||||
- ✅ 文件名显示和编辑
|
||||
- ✅ 自动添加 .md 扩展名
|
||||
|
||||
### 4. 文件操作
|
||||
- ✅ 保存修改到服务器(POST 请求)
|
||||
- ✅ 提交新文件到服务器
|
||||
- ✅ 文件内容验证
|
||||
- ✅ 操作成功/失败提示
|
||||
|
||||
### 5. 用户界面
|
||||
- ✅ 直观的分割窗口布局
|
||||
- ✅ 左侧文件列表
|
||||
- ✅ 右侧编辑区域
|
||||
- ✅ 顶部服务器配置栏
|
||||
- ✅ 底部操作按钮
|
||||
- ✅ 状态栏显示
|
||||
- ✅ 错误提示对话框
|
||||
- ✅ 成功提示对话框
|
||||
|
||||
## 技术特性
|
||||
|
||||
### API 集成
|
||||
- ✅ GET /list - 获取文件列表
|
||||
- ✅ GET /{filename}/get - 下载文件内容
|
||||
- ✅ POST /{filename}/post - 上传文件内容
|
||||
- ✅ 请求超时处理(10秒)
|
||||
- ✅ 错误处理和异常捕获
|
||||
|
||||
### 测试覆盖
|
||||
- ✅ 窗口初始化测试
|
||||
- ✅ UI 组件测试
|
||||
- ✅ 服务器配置测试
|
||||
- ✅ 文件操作测试
|
||||
- ✅ 边界条件测试
|
||||
- ✅ Mock 服务器响应测试
|
||||
|
||||
### 用户体验
|
||||
- ✅ 自动依赖检查
|
||||
- ✅ 一键启动脚本
|
||||
- ✅ 一键测试脚本
|
||||
- ✅ 中文本地化
|
||||
- ✅ 友好的错误提示
|
||||
- ✅ 实时状态更新
|
||||
|
||||
## 依赖项
|
||||
|
||||
- PySide6 6.6.0 - Qt6 GUI 框架
|
||||
- requests 2.31.0 - HTTP 客户端
|
||||
- pytest 7.4.3 - 测试框架
|
||||
- pytest-qt 4.2.0 - Qt 应用测试
|
||||
|
||||
## 项目文件
|
||||
|
||||
- main_window.py - 主应用程序
|
||||
- api_client.py - API 客户端
|
||||
- test_app.py - 测试套件
|
||||
- requirements.txt - 依赖列表
|
||||
- run.bat - 启动脚本
|
||||
- run_tests.bat - 测试脚本
|
||||
- README.md - 使用文档
|
||||
- FEATURES.md - 功能列表(本文件)
|
||||
96
README.md
Normal file
96
README.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# 知识库管理器
|
||||
|
||||
基于 PySide6 的知识库管理应用程序,用于连接 FastAPI 知识库服务器,查看、编辑和提交 Markdown 格式的知识库文件。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 服务器地址配置和连接测试
|
||||
- 获取服务器上的文件列表(仅显示 .md 文件)
|
||||
- 查看和编辑 Markdown 文件内容
|
||||
- 保存修改到服务器
|
||||
- 提交新的知识库文件
|
||||
- 直观的用户界面,支持分割窗口
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 运行应用程序
|
||||
|
||||
### Windows 用户
|
||||
|
||||
双击 `run.bat` 文件启动应用程序。
|
||||
|
||||
### 命令行运行
|
||||
|
||||
```bash
|
||||
python main_window.py
|
||||
```
|
||||
|
||||
## 运行测试
|
||||
|
||||
### Windows 用户
|
||||
|
||||
双击 `run_tests.bat` 文件运行测试。
|
||||
|
||||
### 命令行运行
|
||||
|
||||
```bash
|
||||
pytest test_app.py -v
|
||||
```
|
||||
|
||||
## API 端点
|
||||
|
||||
应用程序使用以下 API 端点:
|
||||
|
||||
- `GET /list` - 获取文件列表
|
||||
- `GET /{filename}/get` - 下载文件内容
|
||||
- `POST /{filename}/post` - 上传文件内容
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. **配置服务器**:
|
||||
- 在"服务器地址"输入框中输入服务器地址(例如:`http://43.134.1.17:8800/`)
|
||||
- 点击"配置"按钮连接服务器
|
||||
|
||||
2. **查看文件列表**:
|
||||
- 配置成功后,点击"刷新列表"按钮
|
||||
- 文件列表将显示所有 .md 文件
|
||||
|
||||
3. **编辑文件**:
|
||||
- 在左侧文件列表中点击要编辑的文件
|
||||
- 文件内容将显示在右侧编辑器中
|
||||
- 编辑完成后点击"保存到服务器"
|
||||
|
||||
4. **提交新文件**:
|
||||
- 在"文件名"输入框中输入新文件名(自动添加 .md 扩展名)
|
||||
- 在编辑器中输入文件内容
|
||||
- 点击"提交新文件"按钮
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
.
|
||||
├── main_window.py # 主应用程序窗口
|
||||
├── api_client.py # API 客户端模块
|
||||
├── test_app.py # pytest-qt 测试用例
|
||||
├── requirements.txt # Python 依赖
|
||||
├── run.bat # Windows 启动脚本
|
||||
├── run_tests.bat # Windows 测试脚本
|
||||
└── README.md # 本文件
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **UI 框架**: PySide6 (Qt6 的官方 Python 绑定)
|
||||
- **HTTP 客户端**: requests
|
||||
- **测试框架**: pytest, pytest-qt
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保服务器地址正确且可访问
|
||||
- 文件名会自动添加 .md 扩展名
|
||||
- 网络操作有超时设置(10秒)
|
||||
- 所有文件操作都有错误提示
|
||||
43
api_client.py
Normal file
43
api_client.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import requests
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class KnowledgeBaseAPI:
|
||||
def __init__(self, base_url: str):
|
||||
self.base_url = base_url.rstrip('/')
|
||||
|
||||
def get_file_list(self) -> List[str]:
|
||||
"""获取服务器上的文件列表"""
|
||||
try:
|
||||
response = requests.get(f"{self.base_url}/list", timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.RequestException as e:
|
||||
raise Exception(f"获取文件列表失败: {str(e)}")
|
||||
|
||||
def get_file(self, filename: str) -> str:
|
||||
"""获取文件内容"""
|
||||
try:
|
||||
response = requests.get(f"{self.base_url}/{filename}/get", timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
except requests.RequestException as e:
|
||||
raise Exception(f"获取文件 {filename} 失败: {str(e)}")
|
||||
|
||||
def post_file(self, filename: str, content: str) -> bool:
|
||||
"""上传文件内容"""
|
||||
try:
|
||||
files = {'file': (filename, content, 'text/markdown')}
|
||||
response = requests.post(f"{self.base_url}/{filename}/post", files=files, timeout=10)
|
||||
response.raise_for_status()
|
||||
return True
|
||||
except requests.RequestException as e:
|
||||
raise Exception(f"上传文件 {filename} 失败: {str(e)}")
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
"""测试服务器连接"""
|
||||
try:
|
||||
response = requests.get(f"{self.base_url}/list", timeout=5)
|
||||
return response.status_code == 200
|
||||
except requests.RequestException:
|
||||
return False
|
||||
2
fastapi的内容.txt
Normal file
2
fastapi的内容.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
{"service":"知识库API服务","version":"1.0.0","endpoints":[{"method":"GET","path":"/list","description":"获取文件列表"},{"method":"GET","path":"/{filename}/get","description":"下载文件"},{"method":"POST","path":"/{filename}/post","description":"上传文件"}],"knowledge_base_path":"/root/clawd/knowledge_base","port":8800,"usage_examples":{"list_files":"curl -X GET http://[服务器IP]:8800/list","download_file":"curl -X GET http://[服务器IP]:8800/example.txt/get -o example.txt","upload_file":"curl -X POST -F 'file=@local_file.txt' http://[服务器IP]:8800/remote_name.txt/post"}}
|
||||
|
||||
203
main_window.py
Normal file
203
main_window.py
Normal file
@@ -0,0 +1,203 @@
|
||||
import sys
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QLineEdit, QPushButton, QListWidget, QTextEdit,
|
||||
QMessageBox, QSplitter, QFileDialog
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
from api_client import KnowledgeBaseAPI
|
||||
|
||||
|
||||
class KnowledgeBaseApp(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.api: Optional[KnowledgeBaseAPI] = None
|
||||
self.current_filename: Optional[str] = None
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
self.setWindowTitle('知识库管理器')
|
||||
self.setGeometry(100, 100, 1200, 800)
|
||||
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
|
||||
main_layout = QVBoxLayout(central_widget)
|
||||
|
||||
# 服务器地址配置区域
|
||||
server_layout = QHBoxLayout()
|
||||
server_label = QLabel('服务器地址:')
|
||||
self.server_input = QLineEdit('http://43.134.1.17:8800/')
|
||||
self.server_input.setMinimumWidth(300)
|
||||
self.config_button = QPushButton('配置')
|
||||
self.config_button.clicked.connect(self.configure_server)
|
||||
self.refresh_button = QPushButton('刷新列表')
|
||||
self.refresh_button.clicked.connect(self.refresh_file_list)
|
||||
|
||||
server_layout.addWidget(server_label)
|
||||
server_layout.addWidget(self.server_input)
|
||||
server_layout.addWidget(self.config_button)
|
||||
server_layout.addWidget(self.refresh_button)
|
||||
server_layout.addStretch()
|
||||
|
||||
main_layout.addLayout(server_layout)
|
||||
|
||||
# 主内容区域(使用分割器)
|
||||
splitter = QSplitter(Qt.Horizontal)
|
||||
|
||||
# 左侧:文件列表
|
||||
left_widget = QWidget()
|
||||
left_layout = QVBoxLayout(left_widget)
|
||||
|
||||
list_label = QLabel('文件列表 (.md):')
|
||||
self.file_list = QListWidget()
|
||||
self.file_list.itemClicked.connect(self.load_file)
|
||||
|
||||
left_layout.addWidget(list_label)
|
||||
left_layout.addWidget(self.file_list)
|
||||
|
||||
# 右侧:编辑区域
|
||||
right_widget = QWidget()
|
||||
right_layout = QVBoxLayout(right_widget)
|
||||
|
||||
filename_layout = QHBoxLayout()
|
||||
filename_label = QLabel('文件名:')
|
||||
self.filename_input = QLineEdit()
|
||||
self.filename_input.setPlaceholderText('输入文件名(例如:example.md)')
|
||||
|
||||
filename_layout.addWidget(filename_label)
|
||||
filename_layout.addWidget(self.filename_input)
|
||||
|
||||
self.text_editor = QTextEdit()
|
||||
self.text_editor.setPlaceholderText('在此编辑 Markdown 内容...')
|
||||
|
||||
button_layout = QHBoxLayout()
|
||||
self.save_button = QPushButton('保存到服务器')
|
||||
self.save_button.clicked.connect(self.save_file)
|
||||
self.post_button = QPushButton('提交新文件')
|
||||
self.post_button.clicked.connect(self.post_new_file)
|
||||
|
||||
button_layout.addWidget(self.save_button)
|
||||
button_layout.addWidget(self.post_button)
|
||||
|
||||
right_layout.addLayout(filename_layout)
|
||||
right_layout.addWidget(self.text_editor)
|
||||
right_layout.addLayout(button_layout)
|
||||
|
||||
splitter.addWidget(left_widget)
|
||||
splitter.addWidget(right_widget)
|
||||
splitter.setSizes([300, 900])
|
||||
|
||||
main_layout.addWidget(splitter)
|
||||
|
||||
# 状态栏
|
||||
self.status_label = QLabel('就绪')
|
||||
self.statusBar().addWidget(self.status_label)
|
||||
|
||||
def configure_server(self):
|
||||
server_url = self.server_input.text().strip()
|
||||
if not server_url:
|
||||
QMessageBox.warning(self, '警告', '请输入服务器地址')
|
||||
return
|
||||
|
||||
self.api = KnowledgeBaseAPI(server_url)
|
||||
|
||||
if self.api.test_connection():
|
||||
self.status_label.setText(f'已连接到服务器: {server_url}')
|
||||
QMessageBox.information(self, '成功', '服务器配置成功!')
|
||||
self.refresh_file_list()
|
||||
else:
|
||||
QMessageBox.critical(self, '错误', '无法连接到服务器,请检查地址是否正确')
|
||||
self.status_label.setText('连接失败')
|
||||
|
||||
def refresh_file_list(self):
|
||||
if not self.api:
|
||||
QMessageBox.warning(self, '警告', '请先配置服务器')
|
||||
return
|
||||
|
||||
try:
|
||||
files = self.api.get_file_list()
|
||||
self.file_list.clear()
|
||||
|
||||
# 只显示 .md 文件
|
||||
md_files = [f for f in files if f.endswith('.md')]
|
||||
self.file_list.addItems(md_files)
|
||||
|
||||
self.status_label.setText(f'已加载 {len(md_files)} 个文件')
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, '错误', str(e))
|
||||
self.status_label.setText('加载文件列表失败')
|
||||
|
||||
def load_file(self, item):
|
||||
filename = item.text()
|
||||
self.current_filename = filename
|
||||
self.filename_input.setText(filename)
|
||||
|
||||
try:
|
||||
content = self.api.get_file(filename)
|
||||
self.text_editor.setPlainText(content)
|
||||
self.status_label.setText(f'已加载文件: {filename}')
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, '错误', str(e))
|
||||
self.status_label.setText('加载文件失败')
|
||||
|
||||
def save_file(self):
|
||||
if not self.api:
|
||||
QMessageBox.warning(self, '警告', '请先配置服务器')
|
||||
return
|
||||
|
||||
if not self.current_filename:
|
||||
QMessageBox.warning(self, '警告', '请先选择一个文件')
|
||||
return
|
||||
|
||||
content = self.text_editor.toPlainText()
|
||||
|
||||
try:
|
||||
self.api.post_file(self.current_filename, content)
|
||||
QMessageBox.information(self, '成功', f'文件 {self.current_filename} 保存成功!')
|
||||
self.status_label.setText(f'已保存文件: {self.current_filename}')
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, '错误', str(e))
|
||||
self.status_label.setText('保存文件失败')
|
||||
|
||||
def post_new_file(self):
|
||||
if not self.api:
|
||||
QMessageBox.warning(self, '警告', '请先配置服务器')
|
||||
return
|
||||
|
||||
filename = self.filename_input.text().strip()
|
||||
|
||||
if not filename:
|
||||
QMessageBox.warning(self, '警告', '请输入文件名')
|
||||
return
|
||||
|
||||
if not filename.endswith('.md'):
|
||||
filename += '.md'
|
||||
self.filename_input.setText(filename)
|
||||
|
||||
content = self.text_editor.toPlainText()
|
||||
|
||||
if not content:
|
||||
QMessageBox.warning(self, '警告', '请输入文件内容')
|
||||
return
|
||||
|
||||
try:
|
||||
self.api.post_file(filename, content)
|
||||
QMessageBox.information(self, '成功', f'文件 {filename} 提交成功!')
|
||||
self.current_filename = filename
|
||||
self.status_label.setText(f'已提交文件: {filename}')
|
||||
self.refresh_file_list()
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, '错误', str(e))
|
||||
self.status_label.setText('提交文件失败')
|
||||
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
window = KnowledgeBaseApp()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
PySide6==6.6.0
|
||||
requests==2.31.0
|
||||
pytest-qt==4.2.0
|
||||
pytest==7.4.3
|
||||
43
run.bat
Normal file
43
run.bat
Normal file
@@ -0,0 +1,43 @@
|
||||
@echo off
|
||||
chcp 65001 > nul
|
||||
echo ================================================
|
||||
echo 知识库管理器 - 启动脚本
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
REM 检查 Python 是否安装
|
||||
python --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] 未找到 Python,请先安装 Python 3.8 或更高版本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [1/3] 检查依赖...
|
||||
pip show PySide6 >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [提示] 未安装依赖,正在安装...
|
||||
pip install -r requirements.txt
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] 依赖安装失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
) else (
|
||||
echo [成功] 依赖已安装
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [2/3] 启动应用程序...
|
||||
python main_window.py
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo.
|
||||
echo [错误] 应用程序启动失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [成功] 应用程序已关闭
|
||||
pause
|
||||
43
run_tests.bat
Normal file
43
run_tests.bat
Normal file
@@ -0,0 +1,43 @@
|
||||
@echo off
|
||||
chcp 65001 > nul
|
||||
echo ================================================
|
||||
echo 知识库管理器 - 测试脚本
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
REM 检查 Python 是否安装
|
||||
python --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] 未找到 Python,请先安装 Python 3.8 或更高版本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [1/3] 检查依赖...
|
||||
pip show pytest-qt >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [提示] 未安装依赖,正在安装...
|
||||
pip install -r requirements.txt
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] 依赖安装失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
) else (
|
||||
echo [成功] 依赖已安装
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [2/3] 运行测试...
|
||||
pytest test_app.py -v
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo.
|
||||
echo [警告] 部分测试失败
|
||||
) else (
|
||||
echo.
|
||||
echo [成功] 所有测试通过
|
||||
)
|
||||
|
||||
echo.
|
||||
pause
|
||||
180
test_app.py
Normal file
180
test_app.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import pytest
|
||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||
from PySide6.QtCore import Qt
|
||||
from unittest.mock import Mock, patch
|
||||
from main_window import KnowledgeBaseApp
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def app():
|
||||
"""创建 QApplication 实例"""
|
||||
if not QApplication.instance():
|
||||
app = QApplication([])
|
||||
else:
|
||||
app = QApplication.instance()
|
||||
yield app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def main_window(app, qtbot):
|
||||
"""创建主窗口实例"""
|
||||
window = KnowledgeBaseApp()
|
||||
qtbot.addWidget(window)
|
||||
window.show()
|
||||
return window
|
||||
|
||||
|
||||
def test_window_initialization(main_window, qtbot):
|
||||
"""测试窗口初始化"""
|
||||
assert main_window.windowTitle() == '知识库管理器'
|
||||
assert main_window.server_input.text() == 'http://43.134.1.17:8800/'
|
||||
assert main_window.config_button.text() == '配置'
|
||||
assert main_window.refresh_button.text() == '刷新列表'
|
||||
assert main_window.save_button.text() == '保存到服务器'
|
||||
assert main_window.post_button.text() == '提交新文件'
|
||||
|
||||
|
||||
def test_server_input_exists(main_window, qtbot):
|
||||
"""测试服务器地址输入框"""
|
||||
assert main_window.server_input is not None
|
||||
assert main_window.server_input.placeholderText() == ''
|
||||
qtbot.keyClicks(main_window.server_input, 'http://test.com')
|
||||
assert 'http://test.com' in main_window.server_input.text()
|
||||
|
||||
|
||||
def test_filename_input_exists(main_window, qtbot):
|
||||
"""测试文件名输入框"""
|
||||
assert main_window.filename_input is not None
|
||||
assert main_window.filename_input.placeholderText() == '输入文件名(例如:example.md)'
|
||||
qtbot.keyClicks(main_window.filename_input, 'test.md')
|
||||
assert main_window.filename_input.text() == 'test.md'
|
||||
|
||||
|
||||
def test_text_editor_exists(main_window, qtbot):
|
||||
"""测试文本编辑器"""
|
||||
assert main_window.text_editor is not None
|
||||
test_content = '# Test Markdown\n\nThis is a test.'
|
||||
main_window.text_editor.setPlainText(test_content)
|
||||
assert main_window.text_editor.toPlainText() == test_content
|
||||
|
||||
|
||||
def test_file_list_exists(main_window):
|
||||
"""测试文件列表"""
|
||||
assert main_window.file_list is not None
|
||||
assert main_window.file_list.count() == 0
|
||||
|
||||
|
||||
def test_buttons_clickable(main_window, qtbot):
|
||||
"""测试按钮可点击"""
|
||||
assert main_window.config_button.isEnabled()
|
||||
assert main_window.refresh_button.isEnabled()
|
||||
assert main_window.save_button.isEnabled()
|
||||
assert main_window.post_button.isEnabled()
|
||||
|
||||
|
||||
def test_configure_server_without_url(main_window, qtbot):
|
||||
"""测试配置服务器(无URL)"""
|
||||
main_window.server_input.clear()
|
||||
|
||||
with patch.object(QMessageBox, 'warning') as mock_warning:
|
||||
main_window.configure_server()
|
||||
mock_warning.assert_called_once()
|
||||
assert main_window.api is None
|
||||
|
||||
|
||||
def test_configure_server_invalid_url(main_window, qtbot):
|
||||
"""测试配置服务器(无效URL)"""
|
||||
main_window.server_input.setText('http://invalid-url-that-does-not-exist.com')
|
||||
|
||||
with patch.object(QMessageBox, 'critical') as mock_critical:
|
||||
main_window.configure_server()
|
||||
mock_critical.assert_called_once()
|
||||
|
||||
|
||||
def test_refresh_file_list_without_api(main_window, qtbot):
|
||||
"""测试刷新文件列表(未配置服务器)"""
|
||||
main_window.api = None
|
||||
|
||||
with patch.object(QMessageBox, 'warning') as mock_warning:
|
||||
main_window.refresh_file_list()
|
||||
mock_warning.assert_called_once()
|
||||
|
||||
|
||||
def test_save_file_without_api(main_window, qtbot):
|
||||
"""测试保存文件(未配置服务器)"""
|
||||
main_window.api = None
|
||||
|
||||
with patch.object(QMessageBox, 'warning') as mock_warning:
|
||||
main_window.save_file()
|
||||
mock_warning.assert_called_once()
|
||||
|
||||
|
||||
def test_save_file_without_selection(main_window, qtbot):
|
||||
"""测试保存文件(未选择文件)"""
|
||||
main_window.api = Mock()
|
||||
main_window.current_filename = None
|
||||
|
||||
with patch.object(QMessageBox, 'warning') as mock_warning:
|
||||
main_window.save_file()
|
||||
mock_warning.assert_called_once()
|
||||
|
||||
|
||||
def test_post_new_file_without_api(main_window, qtbot):
|
||||
"""测试提交新文件(未配置服务器)"""
|
||||
main_window.api = None
|
||||
|
||||
with patch.object(QMessageBox, 'warning') as mock_warning:
|
||||
main_window.post_new_file()
|
||||
mock_warning.assert_called_once()
|
||||
|
||||
|
||||
def test_post_new_file_without_filename(main_window, qtbot):
|
||||
"""测试提交新文件(无文件名)"""
|
||||
main_window.api = Mock()
|
||||
main_window.filename_input.clear()
|
||||
|
||||
with patch.object(QMessageBox, 'warning') as mock_warning:
|
||||
main_window.post_new_file()
|
||||
mock_warning.assert_called_once()
|
||||
|
||||
|
||||
def test_post_new_file_without_content(main_window, qtbot):
|
||||
"""测试提交新文件(无内容)"""
|
||||
main_window.api = Mock()
|
||||
main_window.filename_input.setText('test.md')
|
||||
main_window.text_editor.clear()
|
||||
|
||||
with patch.object(QMessageBox, 'warning') as mock_warning:
|
||||
main_window.post_new_file()
|
||||
mock_warning.assert_called_once()
|
||||
|
||||
|
||||
def test_post_new_file_auto_add_extension(main_window, qtbot):
|
||||
"""测试提交新文件(自动添加.md扩展名)"""
|
||||
main_window.api = Mock()
|
||||
main_window.filename_input.setText('test')
|
||||
main_window.text_editor.setPlainText('# Test')
|
||||
|
||||
with patch.object(QMessageBox, 'information') as mock_info:
|
||||
with patch.object(main_window, 'refresh_file_list'):
|
||||
main_window.post_new_file()
|
||||
assert main_window.filename_input.text() == 'test.md'
|
||||
|
||||
|
||||
def test_ui_layout_elements(main_window):
|
||||
"""测试UI布局元素"""
|
||||
assert main_window.server_input.parent() is not None
|
||||
assert main_window.file_list.parent() is not None
|
||||
assert main_window.text_editor.parent() is not None
|
||||
assert main_window.status_label.parent() is not None
|
||||
|
||||
|
||||
def test_status_label_updates(main_window, qtbot):
|
||||
"""测试状态标签更新"""
|
||||
initial_text = main_window.status_label.text()
|
||||
main_window.status_label.setText('测试状态')
|
||||
assert main_window.status_label.text() == '测试状态'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
Reference in New Issue
Block a user