128 lines
6.0 KiB
Python
128 lines
6.0 KiB
Python
import json
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
|
|
def test_knowledge_action_handler_delegates_to_dispatch(tmp_path):
|
|
from channel.web.web_channel import KnowledgeActionHandler
|
|
|
|
request = {"action": "create_category", "payload": {"path": "research"}}
|
|
dispatched = {"action": "create_category", "code": 200, "message": "success",
|
|
"payload": {"path": "research", "created": True}}
|
|
|
|
with patch("channel.web.web_channel._require_auth"), \
|
|
patch("channel.web.web_channel.web.header"), \
|
|
patch("channel.web.web_channel.web.data", return_value=json.dumps(request).encode()), \
|
|
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(KnowledgeActionHandler().POST())
|
|
|
|
dispatch.assert_called_once_with("create_category", {"path": "research"})
|
|
assert response["status"] == "success"
|
|
assert response["payload"]["created"] is True
|
|
|
|
|
|
def test_knowledge_action_handler_preserves_dispatch_error(tmp_path):
|
|
from channel.web.web_channel import KnowledgeActionHandler
|
|
|
|
dispatched = {"action": "delete_documents", "code": 403,
|
|
"message": "protected knowledge file: index.md", "payload": None}
|
|
request = {"action": "delete_documents", "payload": {"paths": ["index.md"]}}
|
|
|
|
with patch("channel.web.web_channel._require_auth"), \
|
|
patch("channel.web.web_channel.web.header"), \
|
|
patch("channel.web.web_channel.web.data", return_value=json.dumps(request).encode()), \
|
|
patch("channel.web.web_channel._get_workspace_root", return_value=str(tmp_path)), \
|
|
patch("agent.knowledge.service.KnowledgeService.dispatch", return_value=dispatched):
|
|
response = json.loads(KnowledgeActionHandler().POST())
|
|
|
|
assert response["status"] == "error"
|
|
assert response["code"] == 403
|
|
assert response["message"] == "protected knowledge file: index.md"
|
|
|
|
|
|
def test_knowledge_frontend_management_contract():
|
|
root = Path(__file__).parents[1]
|
|
html = (root / "channel/web/chat.html").read_text(encoding="utf-8")
|
|
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"
|