第一批:上传去限制+进度展示、模板HTML预览+按需导出、检索TAB实时更新
第二批:edit护栏移除、导出按钮纯化+file_name落库、编辑失焦还原、
二次预览白底根治(防竞态+渲染兜底)
模板体系:7套公文模板(templates/)+templating渲染器、websearch联网
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
129 lines
4.5 KiB
Python
129 lines
4.5 KiB
Python
"""test_vault.py —— 主库:加密落盘无明文断言 + 增删检索(假 embed)。"""
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
|
||
from vstore.vault import Vault, VaultError, get_embed_fn, set_embed_fn
|
||
|
||
|
||
@pytest.fixture
|
||
def vault(tmp_path, embed_fn):
|
||
v = Vault(tmp_path / "vault", tmp_path / "index")
|
||
return v
|
||
|
||
|
||
def _read_all_bytes(path) -> bytes:
|
||
if path.is_dir():
|
||
data = b""
|
||
for f in sorted(path.rglob("*")):
|
||
if f.is_file():
|
||
data += f.read_bytes()
|
||
return data
|
||
return path.read_bytes()
|
||
|
||
|
||
def test_add_encrypted_no_plaintext_on_disk(vault):
|
||
content = "小麦育种试验报告。这是绝密明文,绝不能出现在磁盘上。".encode("utf-8")
|
||
item = vault.add(content, "report.txt", {"name": "试验报告", "description": "小麦"}, as_template=False)
|
||
assert item["document_id"]
|
||
# 全部落盘字节(vault + index)不得包含明文
|
||
for path in (vault.vault_dir, vault.index.dir):
|
||
assert "小麦育种试验报告" not in _read_all_bytes(path).decode("utf-8", errors="replace")
|
||
assert "绝密明文" not in _read_all_bytes(vault.vault_dir).decode("utf-8", errors="replace")
|
||
|
||
|
||
def test_add_enc_is_fernet_token(vault):
|
||
item = vault.add(b"hello doc", "a.txt", {"name": "n"})
|
||
enc = vault.vault_dir / f"{item['document_id']}.enc"
|
||
assert enc.exists()
|
||
assert enc.read_bytes().startswith(b"gAAAA") # Fernet token 前缀
|
||
|
||
|
||
def test_add_and_get_decrypted(vault):
|
||
item = vault.add(
|
||
"正文内容第一段。\n\n第二段。".encode("utf-8"), "doc.txt", {"name": "材料", "category": "资料"}
|
||
)
|
||
doc = vault.get_decrypted(item["document_id"])
|
||
assert doc["meta"]["name"] == "材料"
|
||
assert "正文内容第一段" in doc["extracted"]["paragraphs"][0]
|
||
json_doc = vault.get_document_json(item["document_id"])
|
||
assert json_doc["title"] == "正文内容第一段。"
|
||
|
||
|
||
def test_add_empty_rejected(vault):
|
||
with pytest.raises(VaultError):
|
||
vault.add(b"", "a.txt", {})
|
||
|
||
|
||
def test_add_oversize_accepted(vault):
|
||
# 2026-09-04 手工测试:上传不再设大小门槛,21MB 应正常入库
|
||
big = b"x" * (21 * 1024 * 1024)
|
||
item = vault.add(big, "a.txt", {})
|
||
assert item["document_id"]
|
||
doc = vault.get_decrypted(item["document_id"])
|
||
assert doc["meta"]["size"] == len(big)
|
||
|
||
|
||
def test_add_bad_docx_rejected(vault):
|
||
with pytest.raises(VaultError):
|
||
vault.add(b"not a docx", "a.docx", {})
|
||
|
||
|
||
def test_delete_removes_enc_and_index(vault):
|
||
item = vault.add(b"content", "a.txt", {"name": "n"})
|
||
doc_id = item["document_id"]
|
||
assert vault.delete(doc_id) is True
|
||
assert not (vault.vault_dir / f"{doc_id}.enc").exists()
|
||
assert vault.get_decrypted(doc_id) is None
|
||
assert vault.delete("nope") is False
|
||
|
||
|
||
def test_search_uses_fake_embed(vault):
|
||
vault.add("小麦内容。".encode("utf-8"), "a.txt", {"name": "小麦", "description": "小麦育种"})
|
||
vault.add("水稻内容。".encode("utf-8"), "b.txt", {"name": "水稻", "description": "水稻栽培"})
|
||
result = vault.search("水稻", top_k=2)
|
||
assert result["mode"] in ("vector", "keyword", "latest")
|
||
assert len(result["items"]) >= 1
|
||
|
||
|
||
def test_rebuild_index_from_enc(vault):
|
||
vault.add("甲内容。".encode("utf-8"), "a.txt", {"name": "甲", "as_template": True})
|
||
vault.add("乙内容。".encode("utf-8"), "b.txt", {"name": "乙"})
|
||
rebuilt = vault.rebuild_index()
|
||
assert rebuilt["status"] == "ok"
|
||
assert rebuilt["count"] == 2
|
||
# 重建后可检索
|
||
result = vault.search("乙")
|
||
assert len(result["items"]) >= 1
|
||
|
||
|
||
def test_get_style_missing_returns_empty(vault):
|
||
item = vault.add(b"plain", "a.txt", {"name": "n"})
|
||
assert vault.get_style(item["document_id"]) == {}
|
||
|
||
|
||
def test_update_metadata(vault):
|
||
item = vault.add(b"x", "a.txt", {"name": "n"})
|
||
assert vault.update_metadata(item["document_id"], {"name": "改名"}) is True
|
||
rows = vault.list_rows()
|
||
assert rows[0]["name"] == "改名"
|
||
|
||
|
||
def test_template_outline_chunks_limited(vault):
|
||
# 大纲超长 → 切块 ≤ MAX_OUTLINE_CHUNKS;embed_chunks 反映切块数
|
||
from vstore.parsing import extract_docx # 用 docx 造带大纲文档
|
||
|
||
import io
|
||
|
||
from docx import Document
|
||
|
||
doc = Document()
|
||
for i in range(12):
|
||
doc.add_heading(f"第{i}章" + "内容" * 100, level=1)
|
||
doc.add_paragraph("正文。")
|
||
buf = io.BytesIO()
|
||
doc.save(buf)
|
||
|
||
item = vault.add(buf.getvalue(), "t.docx", {"name": "长模板"}, as_template=True)
|
||
assert 1 <= item["embed_chunks"] <= 3
|