feat(server): add workbench API, DSH planner worker and RAG pipeline
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""自动备份/恢复演练单元测试(临时目录,不碰真实数据)。"""
|
||||
import os
|
||||
import sqlite3
|
||||
import zipfile
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app import backup as bk
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def isolated_backup(monkeypatch, tmp_path):
|
||||
"""隔离 DATA_DIR/KB_DIR/BACKUP_DIR 到临时目录,并造一个含业务表的库。"""
|
||||
data_dir = tmp_path / "data"
|
||||
data_dir.mkdir()
|
||||
db_path = data_dir / "workspace.db"
|
||||
with sqlite3.connect(str(db_path)) as conn:
|
||||
conn.executescript("""
|
||||
CREATE TABLE campaign (id INTEGER PRIMARY KEY, name TEXT);
|
||||
CREATE TABLE agent (id INTEGER PRIMARY KEY);
|
||||
CREATE TABLE appuser (id INTEGER PRIMARY KEY);
|
||||
CREATE TABLE other (x INTEGER);
|
||||
INSERT INTO campaign (name) VALUES ('任务一'), ('任务二');
|
||||
""")
|
||||
kb_dir = data_dir / "kb"
|
||||
kb_dir.mkdir()
|
||||
(kb_dir / "note.txt").write_text("知识内容", encoding="utf-8")
|
||||
monkeypatch.setattr(bk, "DATA_DIR", data_dir)
|
||||
monkeypatch.setattr(bk, "KB_DIR", kb_dir)
|
||||
monkeypatch.setattr(bk, "BACKUP_DIR", data_dir / "backups")
|
||||
return data_dir
|
||||
|
||||
|
||||
def test_create_backup_returns_zip(isolated_backup):
|
||||
result = bk.create_backup("test")
|
||||
assert result is not None and result.exists()
|
||||
assert result.name.startswith("backup-test-")
|
||||
with zipfile.ZipFile(result) as zf:
|
||||
assert "workspace.db" in zf.namelist()
|
||||
assert "kb/note.txt" in zf.namelist()
|
||||
|
||||
|
||||
def test_backup_skipped_when_no_db(tmp_path):
|
||||
empty = tmp_path / "empty"
|
||||
empty.mkdir()
|
||||
(empty / "kb").mkdir()
|
||||
with patch.object(bk, "DATA_DIR", empty), patch.object(bk, "KB_DIR", empty / "kb"), \
|
||||
patch.object(bk, "BACKUP_DIR", empty / "backups"):
|
||||
assert bk.create_backup("x") is None
|
||||
|
||||
|
||||
def test_verify_backup_checks_tables(isolated_backup):
|
||||
result = bk.create_backup("test")
|
||||
ok, detail = bk.verify_backup(result)
|
||||
assert ok, detail
|
||||
assert "campaign 2 行" in detail
|
||||
|
||||
|
||||
def test_verify_backup_rejects_corrupt(isolated_backup):
|
||||
bk.BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
bad = bk.BACKUP_DIR / "backup-bad.zip"
|
||||
bad.write_bytes(b"not a zip")
|
||||
ok, _detail = bk.verify_backup(bad)
|
||||
assert not ok
|
||||
|
||||
|
||||
def test_prune_keeps_recent(isolated_backup, monkeypatch):
|
||||
monkeypatch.setattr(bk, "KEEP_COUNT", 3)
|
||||
bk.BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
for i in range(5):
|
||||
target = bk.BACKUP_DIR / f"backup-auto-2026010{i}-000000.zip"
|
||||
target.write_bytes(b"placeholder")
|
||||
os.utime(target, (1700000000 + i * 100, 1700000000 + i * 100))
|
||||
removed = bk._prune_old_backups()
|
||||
assert removed == 2
|
||||
remaining = list(bk.BACKUP_DIR.glob("backup-*.zip"))
|
||||
assert len(remaining) == 3
|
||||
|
||||
|
||||
def test_restore_drill_latest(isolated_backup):
|
||||
bk.create_backup("drill")
|
||||
report = bk.verify_restore_latest()
|
||||
assert report["ok"], report["detail"]
|
||||
assert "解压演练通过" in report["detail"]
|
||||
|
||||
|
||||
def test_backup_status_summary(isolated_backup):
|
||||
bk.create_backup("status")
|
||||
status = bk.backup_status()
|
||||
assert status["count"] == 1
|
||||
assert status["latest"].startswith("backup-status-")
|
||||
assert status["latest_size_kb"] > 0
|
||||
Reference in New Issue
Block a user