Files
slack-sieve/tests/test_slack_export_extended.py
T
ilia 90b632694e
CI / skip-ci-check (pull_request) Successful in 7s
CI / secret-scan (pull_request) Successful in 6s
CI / python-ci (pull_request) Successful in 21s
Redact Slack xoxe tokens from fixtures and allowlist for gitleaks.
Export URL tokens in mini-export fixtures tripped secret-scan; replace with placeholders, stop regenerating them, and allowlist fixture paths for pre-redaction history.
2026-07-10 13:45:02 -04:00

204 lines
6.6 KiB
Python

"""Additional slack_export coverage — normalize, index, pagination, search."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from slack_export import SlackExport
@pytest.fixture
def export_with_synthetic(tmp_path, fixture_export_path):
"""Mini export plus synthetic message edge cases."""
ch = tmp_path / "synthetic"
ch.mkdir()
(tmp_path / "channels.json").write_text(
json.dumps([{"name": "synthetic", "id": "C1"}]),
encoding="utf-8",
)
(tmp_path / "users.json").write_text(
json.dumps(
[
{
"id": "U1",
"name": "ilia",
"profile": {"display_name": "Ilia", "real_name": "Ilia L"},
}
]
),
encoding="utf-8",
)
messages = [
{"type": "message", "user": "U1", "ts": "1000.1", "subtype": "channel_join"},
{
"type": "message",
"user": "U1",
"ts": "1000.2",
"subtype": "channel_topic",
"topic": "Rentals",
},
{
"type": "message",
"user": "U1",
"ts": "1000.3",
"subtype": "file_share",
"files": [{"id": "F1", "name": "invoice.pdf", "mimetype": "application/pdf"}],
},
{
"type": "message",
"user": "U1",
"ts": "1000.4",
"thread_ts": "1000.4",
"reply_count": 2,
"text": "Thread root",
},
{
"type": "message",
"user": "U1",
"ts": "1000.5",
"thread_ts": "1000.4",
"text": "Reply",
},
{"type": "message", "bot_id": "B1", "ts": "1000.6", "text": "bot says hi"},
{"type": "message", "user": "U1", "ts": "1000.7", "blocks": [{"type": "section"}]},
{"type": "message", "user": "U1", "ts": "1000.8", "attachments": [{"text": "card"}]},
]
(ch / "2016-10-10.json").write_text(json.dumps(messages), encoding="utf-8")
return SlackExport(tmp_path)
def test_classify_all_file_kinds(export):
assert export.classify_file({"name": "x.pptx"}) == "slides"
assert export.classify_file({"name": "x.mp4", "mimetype": "video/mp4"}) == "video"
assert export.classify_file({"name": "x.mp3"}) == "audio"
assert export.classify_file({"name": "x.zip"}) == "archive"
assert export.classify_file({"name": "readme.md"}) == "text"
assert export.classify_file({"name": "unknown.bin"}) == "other"
def test_normalize_message_subtypes(export_with_synthetic):
msgs = export_with_synthetic.iter_channel_messages("synthetic")
by_ts = {m["ts"]: m for m in msgs}
assert "joined" in by_ts["1000.1"]["text"]
assert "Rentals" in by_ts["1000.2"]["text"]
assert "invoice.pdf" in by_ts["1000.3"]["text"]
assert by_ts["1000.5"]["is_thread_reply"]
assert by_ts["1000.4"]["thread_reply_count"] >= 1
assert by_ts["1000.6"]["is_bot"]
assert by_ts["1000.7"]["text"] == "[rich message]"
assert by_ts["1000.8"]["text"] == "[attachment]"
def test_ensure_file_index(export_with_synthetic):
idx = export_with_synthetic.ensure_file_index()
assert "F1" in idx
assert idx["F1"]["name"] == "invoice.pdf"
def test_file_record_unknown(export):
assert export.file_record("NONEXISTENT_ID_XYZ") is None
def test_channel_messages_page_pagination(export):
page = export.channel_messages_page("zoey", limit=2, offset=0)
assert page["channel"] == "zoey"
assert len(page["messages"]) <= 2
assert page["total_in_date"] >= len(page["messages"])
if page["has_more"]:
page2 = export.channel_messages_page("zoey", limit=2, offset=2)
assert page2["offset"] == 2
def test_channel_messages_page_empty_channel(tmp_path):
empty = tmp_path / "empty_export"
empty.mkdir()
(empty / "channels.json").write_text("[]", encoding="utf-8")
(empty / "users.json").write_text("[]", encoding="utf-8")
(empty / "ghost").mkdir()
ex = SlackExport(empty)
out = ex.channel_messages_page("ghost")
assert out["messages"] == []
assert out["has_more"] is False
def test_search_messages_channel_scoped(export):
hits = export.search_messages("medical", "doctor", limit=5)
assert isinstance(hits, list)
assert export.search_messages("medical", "", limit=5) == []
def test_search_global_empty_query(export):
out = export.search_global(" ", limit=5)
assert out["channels"] == []
assert out["messages"] == []
def test_search_global_channel_name(export):
out = export.search_global("niagara", limit=10)
assert any(c["name"] == "153niagara" for c in out["channels"])
def test_search_files_with_query(export):
out = export.search_files_global("proof", file_type="all", limit=10)
assert "hits" in out
def test_channel_stats_missing(export):
stats = export.channel_stats("no-such-channel-xyz")
assert stats["exists_in_export"] is False
assert stats["message_count"] == 0
def test_read_messages_invalid_date(export):
assert export.read_messages_file("medical", "2099-99-99") == []
def test_attach_thread_reply_counts(export_with_synthetic):
msgs = export_with_synthetic.iter_channel_messages("synthetic")
assert any(m.get("thread_reply_count", 0) > 0 for m in msgs)
def test_fetch_file_bytes_local(tmp_path, export_with_synthetic):
rec = export_with_synthetic.file_record("F1")
assert rec
local = export_with_synthetic.paths.root / rec["local_relpath"]
local.parent.mkdir(parents=True, exist_ok=True)
local.write_bytes(b"%PDF-1.4 test")
data, meta = export_with_synthetic.fetch_file_bytes("F1")
assert data.startswith(b"%PDF")
assert meta["id"] == "F1"
def test_fetch_file_bytes_remote(export_with_synthetic, monkeypatch):
rec = export_with_synthetic.file_record("F1")
local = export_with_synthetic.paths.root / rec["local_relpath"]
if local.is_file():
local.unlink()
class _Resp:
def read(self):
return b"remote-bytes"
def __enter__(self):
return self
def __exit__(self, *args):
pass
monkeypatch.setattr("urllib.request.urlopen", lambda *a, **k: _Resp())
rec_entry = export_with_synthetic.ensure_file_index()["F1"]
rec_entry["url_private_download"] = "https://files.slack.com/x?token=xoxe-REDACTED"
data, meta = export_with_synthetic.fetch_file_bytes("F1")
assert data == b"remote-bytes"
assert local.is_file()
assert meta["local_exists"] is True
def test_fetch_file_bytes_unknown_id(export):
with pytest.raises(FileNotFoundError, match="Unknown file"):
export.fetch_file_bytes("NO_SUCH_FILE")