mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-18 20:17:09 +08:00
Merge branch 'pr-2915'
This commit is contained in:
@@ -209,3 +209,90 @@ def test_move_does_not_overwrite_target_created_concurrently(tmp_path):
|
||||
assert source.read_text(encoding="utf-8") == "source"
|
||||
assert target.read_text(encoding="utf-8") == "concurrent"
|
||||
assert manager.storage.deleted == []
|
||||
|
||||
|
||||
def test_create_document_writes_and_syncs(tmp_path):
|
||||
svc, manager = service(tmp_path)
|
||||
(tmp_path / "knowledge/notes").mkdir()
|
||||
|
||||
result = svc.dispatch("create_document", {
|
||||
"path": "notes/new.md", "content": "# New\nBody",
|
||||
})
|
||||
|
||||
assert result["code"] == 200
|
||||
assert (tmp_path / "knowledge/notes/new.md").read_text(encoding="utf-8") == "# New\nBody"
|
||||
assert manager.dirty == 1
|
||||
assert manager.synced == 1
|
||||
|
||||
|
||||
def test_import_documents_supports_md_txt_and_rename_conflicts(tmp_path):
|
||||
svc, manager = service(tmp_path)
|
||||
(tmp_path / "knowledge/notes").mkdir()
|
||||
(tmp_path / "knowledge/notes/a.md").write_text("existing", encoding="utf-8")
|
||||
|
||||
result = svc.dispatch("import_documents", {
|
||||
"target_category": "notes",
|
||||
"conflict_strategy": "rename",
|
||||
"files": [
|
||||
{"filename": "a.md", "content": b"# A"},
|
||||
{"filename": "plain.txt", "content": "plain text"},
|
||||
],
|
||||
})
|
||||
|
||||
assert result["code"] == 200
|
||||
assert result["payload"]["imported"] == 2
|
||||
assert (tmp_path / "knowledge/notes/a-1.md").read_text(encoding="utf-8") == "# A"
|
||||
assert (tmp_path / "knowledge/notes/plain.md").read_text(encoding="utf-8") == "plain text"
|
||||
assert manager.storage.deleted == []
|
||||
assert manager.synced == 1
|
||||
|
||||
|
||||
def test_import_documents_skip_overwrite_and_failures(tmp_path):
|
||||
svc, manager = service(tmp_path)
|
||||
(tmp_path / "knowledge/notes").mkdir()
|
||||
existing = tmp_path / "knowledge/notes/a.md"
|
||||
existing.write_text("old", encoding="utf-8")
|
||||
|
||||
skipped = svc.dispatch("import_documents", {
|
||||
"target_category": "notes",
|
||||
"conflict_strategy": "skip",
|
||||
"files": [{"filename": "a.md", "content": b"new"}],
|
||||
})
|
||||
assert skipped["payload"]["skipped"] == 1
|
||||
assert existing.read_text(encoding="utf-8") == "old"
|
||||
assert manager.synced == 0
|
||||
|
||||
overwritten = svc.dispatch("import_documents", {
|
||||
"target_category": "notes",
|
||||
"conflict_strategy": "overwrite",
|
||||
"files": [
|
||||
{"filename": "a.md", "content": b"new"},
|
||||
{"filename": "bad.pdf", "content": b"%PDF"},
|
||||
],
|
||||
})
|
||||
assert overwritten["payload"]["imported"] == 1
|
||||
assert overwritten["payload"]["failed"] == 1
|
||||
assert existing.read_text(encoding="utf-8") == "new"
|
||||
assert manager.storage.deleted == ["knowledge/notes/a.md"]
|
||||
assert manager.synced == 1
|
||||
|
||||
|
||||
def test_import_documents_rejects_large_files_and_batches(tmp_path):
|
||||
svc, manager = service(tmp_path)
|
||||
(tmp_path / "knowledge/notes").mkdir()
|
||||
assert svc.MAX_IMPORT_TOTAL_SIZE == 200 * 1024 * 1024
|
||||
|
||||
too_large = svc.dispatch("import_documents", {
|
||||
"target_category": "notes",
|
||||
"files": [{"filename": "big.md", "content": b"x" * (svc.MAX_IMPORT_FILE_SIZE + 1)}],
|
||||
})
|
||||
assert too_large["payload"]["failed"] == 1
|
||||
assert too_large["payload"]["results"][0]["reason"] == "file too large"
|
||||
|
||||
too_many = svc.dispatch("import_documents", {
|
||||
"target_category": "notes",
|
||||
"files": [{"filename": f"{i}.md", "content": b"x"} for i in range(svc.MAX_IMPORT_FILES + 1)],
|
||||
})
|
||||
assert too_many["code"] == 403
|
||||
assert "too many files" in too_many["message"]
|
||||
assert manager.synced == 0
|
||||
|
||||
@@ -47,15 +47,81 @@ def test_knowledge_frontend_management_contract():
|
||||
js = (root / "channel/web/static/js/console.js").read_text(encoding="utf-8")
|
||||
|
||||
assert 'id="knowledge-dialog-overlay"' in html
|
||||
assert 'id="knowledge-dialog-textarea"' in html
|
||||
assert 'id="knowledge-document-form"' in html
|
||||
assert 'id="knowledge-document-path-preview"' in html
|
||||
assert "function openKnowledgeDialog(" in js
|
||||
assert "function _knowledgeCategoryPaths(" in js
|
||||
assert "dispatchKnowledgeAction('create_category'" in js
|
||||
assert "dispatchKnowledgeAction('create_document'" in js
|
||||
assert "dispatchKnowledgeAction('rename_category'" in js
|
||||
assert "dispatchKnowledgeAction('delete_category'" in js
|
||||
assert "dispatchKnowledgeAction('delete_documents'" in js
|
||||
assert "dispatchKnowledgeAction('move_documents'" in js
|
||||
assert 'id="knowledge-import-input"' in html
|
||||
assert "function createKnowledgeDocument(" in js
|
||||
assert "function openKnowledgeDocumentEditor(" in js
|
||||
assert "documentPathPreview.textContent = options.category" in js
|
||||
assert "options.type === 'document'" in js
|
||||
assert "input.classList.toggle('hidden', options.type === 'select' || options.type === 'textarea' || options.type === 'document')" in js
|
||||
assert "function selectKnowledgeImportFiles(" in js
|
||||
assert "function importKnowledgeDocuments(" in js
|
||||
assert "function validateKnowledgeImportFiles(" in js
|
||||
assert "KNOWLEDGE_IMPORT_MAX_FILE_SIZE" in js
|
||||
assert "fetch('/api/knowledge/import'" in js
|
||||
assert "initKnowledgeImportDropZone()" in js
|
||||
|
||||
knowledge_section = js[js.index("// Knowledge View"):js.index("function _hasFilterMatch")]
|
||||
assert "prompt(" not in knowledge_section
|
||||
assert "alert(" not in knowledge_section
|
||||
assert "if (path === 'index.md' || path === 'log.md') return '';" in knowledge_section
|
||||
|
||||
|
||||
class UploadedFile:
|
||||
def __init__(self, filename, content):
|
||||
self.filename = filename
|
||||
self.value = content
|
||||
|
||||
|
||||
def test_knowledge_import_handler_delegates_to_dispatch(tmp_path):
|
||||
from channel.web.web_channel import KnowledgeImportHandler
|
||||
|
||||
dispatched = {"action": "import_documents", "code": 200, "message": "success",
|
||||
"payload": {"imported": 2, "skipped": 0, "failed": 0}}
|
||||
params = {
|
||||
"target_category": "notes",
|
||||
"conflict_strategy": "rename",
|
||||
"files": [UploadedFile("a.md", b"# A"), UploadedFile("b.txt", b"B")],
|
||||
}
|
||||
|
||||
with patch("channel.web.web_channel._require_auth"), \
|
||||
patch("channel.web.web_channel.web.header"), \
|
||||
patch("channel.web.web_channel._raw_web_input", return_value=params), \
|
||||
patch("channel.web.web_channel._get_workspace_root", return_value=str(tmp_path)), \
|
||||
patch("agent.knowledge.service.KnowledgeService.dispatch", return_value=dispatched) as dispatch:
|
||||
response = json.loads(KnowledgeImportHandler().POST())
|
||||
|
||||
dispatch.assert_called_once()
|
||||
action, payload = dispatch.call_args.args
|
||||
assert action == "import_documents"
|
||||
assert payload["target_category"] == "notes"
|
||||
assert payload["conflict_strategy"] == "rename"
|
||||
assert [f["filename"] for f in payload["files"]] == ["a.md", "b.txt"]
|
||||
assert response["status"] == "success"
|
||||
assert response["payload"]["imported"] == 2
|
||||
|
||||
|
||||
def test_knowledge_import_handler_rejects_large_content_length(tmp_path):
|
||||
from channel.web.web_channel import KnowledgeImportHandler
|
||||
from agent.knowledge.service import KnowledgeService
|
||||
assert KnowledgeService.MAX_IMPORT_TOTAL_SIZE == 200 * 1024 * 1024
|
||||
|
||||
with patch("channel.web.web_channel._require_auth"), \
|
||||
patch("channel.web.web_channel.web.header"), \
|
||||
patch("channel.web.web_channel.web.ctx") as ctx:
|
||||
ctx.env = {"CONTENT_LENGTH": str(KnowledgeService.MAX_IMPORT_TOTAL_SIZE + 1)}
|
||||
response = json.loads(KnowledgeImportHandler().POST())
|
||||
|
||||
assert response["status"] == "error"
|
||||
assert response["code"] == 413
|
||||
assert response["message"] == "import batch too large"
|
||||
|
||||
Reference in New Issue
Block a user