Cherry-pick heuristics, manifest merge, global file search, scrollbar styling, and pytest/Playwright coverage unblock review and filtered export for mmetl.
163 lines
4.7 KiB
Python
163 lines
4.7 KiB
Python
"""Additional HTTP API and server module coverage."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
import pytest
|
|
|
|
|
|
def _get_raw(base: str, path: str) -> tuple[int, bytes]:
|
|
try:
|
|
with urllib.request.urlopen(f"{base}{path}", timeout=5) as resp:
|
|
return resp.status, resp.read()
|
|
except urllib.error.HTTPError as exc:
|
|
return exc.code, exc.read()
|
|
|
|
|
|
def test_users_list(api):
|
|
data = api["get"]("/api/users")
|
|
assert isinstance(data["users"], list)
|
|
if data["users"]:
|
|
assert "id" in data["users"][0]
|
|
|
|
|
|
def test_channel_search_endpoint(api):
|
|
data = api["get"]("/api/channels/medical/search?q=doctor")
|
|
assert data["channel"] == "medical"
|
|
assert isinstance(data["hits"], list)
|
|
|
|
|
|
def test_channel_messages_paged(api):
|
|
data = api["get"]("/api/channels/zoey/messages?limit=3&offset=0")
|
|
assert data["channel"] == "zoey"
|
|
assert len(data["messages"]) <= 3
|
|
assert "dates" in data
|
|
|
|
|
|
def test_channels_filter_query(api):
|
|
data = api["get"]("/api/channels?q=med")
|
|
names = {c["name"] for c in data["channels"]}
|
|
assert "medical" in names
|
|
|
|
|
|
def test_file_preview_missing_rel(api):
|
|
status, body = _get_raw(api["base"], "/api/file-preview?rel=../outside.txt")
|
|
assert status == 404
|
|
data = json.loads(body.decode())
|
|
assert data["ok"] is False
|
|
|
|
|
|
def test_file_preview_traversal_blocked(api):
|
|
status, _ = _get_raw(api["base"], "/api/file-preview?rel=..%2F..%2Fetc%2Fpasswd")
|
|
assert status in (400, 404)
|
|
|
|
|
|
def test_file_download_bad_id(api):
|
|
status, _ = _get_raw(api["base"], "/api/file-download?file_id=NOPE")
|
|
assert status == 404
|
|
|
|
|
|
def test_file_download_missing_param(api):
|
|
status, _ = _get_raw(api["base"], "/api/file-download")
|
|
assert status == 400
|
|
|
|
|
|
def test_uploads_path_traversal(api):
|
|
status, _ = _get_raw(api["base"], "/uploads/..%2F..%2Fetc%2Fpasswd")
|
|
assert status == 400
|
|
|
|
|
|
def test_static_css(api):
|
|
status, body = _get_raw(api["base"], "/static/app.css")
|
|
assert status == 200
|
|
assert b"message" in body or len(body) > 0
|
|
|
|
|
|
def test_options_cors(api):
|
|
req = urllib.request.Request(
|
|
f"{api['base']}/api/manifest",
|
|
method="OPTIONS",
|
|
)
|
|
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
assert resp.status == 204
|
|
assert resp.headers.get("Access-Control-Allow-Origin") == "*"
|
|
|
|
|
|
def test_manifest_invalid_json(api):
|
|
req = urllib.request.Request(
|
|
f"{api['base']}/api/manifest",
|
|
data=b"{not valid json",
|
|
headers={"Content-Type": "application/json"},
|
|
method="POST",
|
|
)
|
|
with pytest.raises(urllib.error.HTTPError) as exc:
|
|
urllib.request.urlopen(req, timeout=5)
|
|
assert exc.value.code == 400
|
|
|
|
|
|
def test_not_found_route(api):
|
|
status, _ = _get_raw(api["base"], "/api/does-not-exist")
|
|
assert status == 404
|
|
|
|
|
|
def test_manifest_post_users(api):
|
|
users_before = api["get"]("/api/users")["users"]
|
|
assert users_before
|
|
uid = users_before[0]["id"]
|
|
api["post"](
|
|
"/api/manifest",
|
|
{
|
|
"users": {
|
|
uid: {
|
|
"import": True,
|
|
"notes": "mapped",
|
|
"mattermost_username": "testuser",
|
|
}
|
|
}
|
|
},
|
|
)
|
|
users = api["get"]("/api/users")["users"]
|
|
match = next((u for u in users if u["id"] == uid), None)
|
|
assert match and match["import"] and match["mattermost_username"] == "testuser"
|
|
|
|
|
|
def test_manifest_post_invalid_channel_entry_skipped(api):
|
|
api["post"](
|
|
"/api/manifest",
|
|
{"channels": {"bad": "not-a-dict", "medical": {"status": "partial", "import_mode": "pick"}}},
|
|
)
|
|
med = api["get"]("/api/manifest")["channels"]["medical"]
|
|
assert med["status"] == "partial"
|
|
|
|
|
|
def test_file_download_local_upload(api, tmp_path, review_server):
|
|
"""Download endpoint serves bytes when upload is on disk."""
|
|
import sys
|
|
|
|
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[1]))
|
|
from slack_export import SlackExport
|
|
|
|
root = tmp_path / "dl_export"
|
|
root.mkdir()
|
|
(root / "channels.json").write_text("[]", encoding="utf-8")
|
|
(root / "users.json").write_text("[]", encoding="utf-8")
|
|
up = root / "__uploads" / "FID1"
|
|
up.mkdir(parents=True)
|
|
(up / "doc.pdf").write_bytes(b"%PDF-1.4")
|
|
ex = SlackExport(root)
|
|
ex.ensure_file_index()
|
|
ex._file_index["FID1"] = {
|
|
"id": "FID1",
|
|
"name": "doc.pdf",
|
|
"local_relpath": "__uploads/FID1/doc.pdf",
|
|
"local_exists": True,
|
|
"url_private_download": None,
|
|
}
|
|
review_server["httpd"].RequestHandlerClass.export = ex
|
|
status, body = _get_raw(api["base"], "/api/file-download?file_id=FID1")
|
|
assert status == 200
|
|
assert body.startswith(b"%PDF")
|