2cab53bc94
Test Vector Database Adaptors / Test MCP Vector DB Tools (push) Has been cancelled
Tests / Code Quality (Ruff & Mypy) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.12) (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers CLI - Convert documentation to AI skills dockerfile:Dockerfile name:skill-seekers]) (push) Has been cancelled
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers MCP Server - 25 tools for AI assistants dockerfile:Dockerfile.mcp name:skill-seekers-mcp]) (push) Has been cancelled
Docker Publish / Test Docker Images (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.10) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.12) (push) Has been cancelled
Tests / Serial / Integration / E2E Tests (push) Has been cancelled
Tests / MCP Server Tests (push) Has been cancelled
Test Vector Database Adaptors / Test chroma Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test faiss Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test qdrant Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test weaviate Adaptor (push) Has been cancelled
375 lines
12 KiB
Python
375 lines
12 KiB
Python
"""Golden-output tests for the chat scraper (Phase 2 port).
|
|
|
|
The golden trees under tests/golden/phase2/chat*/ were captured from the
|
|
PRE-DocumentSkillBuilder code; these tests prove the port is byte-identical.
|
|
|
|
Chat sections aren't heading+text shaped like EPUB/Word — they're
|
|
channel+date message groups — so the fixtures exercise chat's own build
|
|
paths: multi-channel categorization, single-channel topic-bucket
|
|
categorization, the single-category main.md fast path, message counts,
|
|
reactions embedded in text, code snippets (incl. >500 chars, empty
|
|
language → "unknown", user attribution), shared links (incl. >20
|
|
truncation), channel summaries, and the empty-export fallback.
|
|
"""
|
|
|
|
import copy
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from tests.phase2_golden_utils import assert_matches_golden, build_snapshot
|
|
|
|
LONG_CODE = "def long_helper():\n" + "\n".join(f" y{i} = {i}" for i in range(60))
|
|
|
|
# ── Multi-channel fixture (slack) ───────────────────────────────────────────
|
|
|
|
ENGINEERING_DAY1_TEXT = "\n\n".join(
|
|
[
|
|
"**alice** (2024-03-01T09:00:00): We hit an error and a bug in the deploy step",
|
|
" Reactions: :thumbsup: (2) :eyes: (1)",
|
|
"**bob** (2024-03-01T09:05:00): Try this fix:\n```python\nprint('patched')\n```",
|
|
"**alice** (2024-03-01T09:10:00): That fixed the crash, thanks!",
|
|
]
|
|
)
|
|
|
|
ENGINEERING_DAY2_TEXT = "\n\n".join(
|
|
[
|
|
"**alice** (2024-03-02T10:00:00): How do we install and setup docker here?",
|
|
"**bob** (2024-03-02T10:15:00): Long helper below:\n```\n" + LONG_CODE + "\n```",
|
|
]
|
|
)
|
|
|
|
RANDOM_DAY1_TEXT = "**carol** (2024-03-01T12:00:00): Lunch at noon? See https://example.com/menu"
|
|
|
|
MULTI_CHANNEL_SECTIONS = [
|
|
{
|
|
"section_number": 1,
|
|
"heading": "#engineering - 2024-03-01",
|
|
"heading_level": "h2",
|
|
"text": ENGINEERING_DAY1_TEXT,
|
|
"headings": [],
|
|
"code_samples": [
|
|
{"code": "print('patched')", "language": "python", "quality_score": 7.0},
|
|
],
|
|
"tables": [],
|
|
"images": [],
|
|
"channel": "engineering",
|
|
"date": "2024-03-01",
|
|
"message_count": 3,
|
|
},
|
|
{
|
|
"section_number": 2,
|
|
"heading": "#engineering - 2024-03-02",
|
|
"heading_level": "h2",
|
|
"text": ENGINEERING_DAY2_TEXT,
|
|
"headings": [],
|
|
"code_samples": [
|
|
{"code": LONG_CODE, "language": "", "quality_score": 9.5},
|
|
],
|
|
"tables": [],
|
|
"images": [],
|
|
"channel": "engineering",
|
|
"date": "2024-03-02",
|
|
"message_count": 2,
|
|
},
|
|
{
|
|
"section_number": 3,
|
|
"heading": "#random - 2024-03-01",
|
|
"heading_level": "h2",
|
|
"text": RANDOM_DAY1_TEXT,
|
|
"headings": [],
|
|
"code_samples": [],
|
|
"tables": [],
|
|
"images": [],
|
|
"channel": "random",
|
|
"date": "2024-03-01",
|
|
"message_count": 1,
|
|
},
|
|
]
|
|
|
|
# 21 links exercises the "... and N more links" truncation in SKILL.md
|
|
MULTI_CHANNEL_LINKS = [
|
|
{
|
|
"url": f"https://example.com/resource/{i}",
|
|
"channel": "engineering" if i % 2 == 0 else "random",
|
|
"user": "alice" if i % 2 == 0 else "carol",
|
|
"timestamp": f"2024-03-01T09:{i:02d}:00",
|
|
"context": f"see https://example.com/resource/{i} for details",
|
|
}
|
|
for i in range(21)
|
|
]
|
|
|
|
MULTI_CHANNEL_DATA = {
|
|
"source": "fixtures/slack-export/",
|
|
"platform": "slack",
|
|
"metadata": {
|
|
"total_messages": 6,
|
|
"total_threads": 1,
|
|
"total_code_snippets": 3,
|
|
"total_links": 21,
|
|
"unique_users": 3,
|
|
"channels": ["engineering", "random"],
|
|
},
|
|
"total_sections": 3,
|
|
"total_code_blocks": 3,
|
|
"channel_summaries": {
|
|
"engineering": {
|
|
"message_count": 5,
|
|
"unique_users": 2,
|
|
"date_range": {
|
|
"earliest": "2024-03-01T09:00:00",
|
|
"latest": "2024-03-02T10:15:00",
|
|
},
|
|
"top_users": [
|
|
{"user": "alice", "count": 3},
|
|
{"user": "bob", "count": 2},
|
|
],
|
|
"has_code": True,
|
|
},
|
|
"random": {
|
|
"message_count": 1,
|
|
"unique_users": 1,
|
|
"date_range": {
|
|
"earliest": "2024-03-01T12:00:00",
|
|
"latest": "2024-03-01T12:00:00",
|
|
},
|
|
"top_users": [{"user": "carol", "count": 1}],
|
|
"has_code": False,
|
|
},
|
|
},
|
|
"code_snippets": [
|
|
{
|
|
"code": LONG_CODE,
|
|
"language": "python",
|
|
"quality_score": 9.5,
|
|
"channel": "engineering",
|
|
"user": "bob",
|
|
"timestamp": "2024-03-02T10:15:00",
|
|
},
|
|
{
|
|
"code": "print('patched')",
|
|
"language": "python",
|
|
"quality_score": 7.0,
|
|
"channel": "engineering",
|
|
"user": "bob",
|
|
"timestamp": "2024-03-01T09:05:00",
|
|
},
|
|
{
|
|
"code": "kubectl get pods",
|
|
"language": "",
|
|
"quality_score": 4.0,
|
|
"channel": "engineering",
|
|
"user": "alice",
|
|
"timestamp": "2024-03-02T11:00:00",
|
|
},
|
|
],
|
|
"links": MULTI_CHANNEL_LINKS,
|
|
"pages": MULTI_CHANNEL_SECTIONS,
|
|
}
|
|
|
|
# ── Single-channel fixture (discord) — topic-bucket categorization ──────────
|
|
|
|
TOPIC_SECTIONS = [
|
|
{
|
|
"section_number": 1,
|
|
"heading": "#help - 2024-04-01",
|
|
"heading_level": "h2",
|
|
"text": "**dave** (2024-04-01T08:00:00): Got an error and a crash, traceback attached",
|
|
"headings": [],
|
|
"code_samples": [],
|
|
"tables": [],
|
|
"images": [],
|
|
"channel": "help",
|
|
"date": "2024-04-01",
|
|
"message_count": 1,
|
|
},
|
|
{
|
|
"section_number": 2,
|
|
"heading": "#help - 2024-04-02",
|
|
"heading_level": "h2",
|
|
"text": "**erin** (2024-04-02T08:00:00): How do I install and setup the docker image?",
|
|
"headings": [],
|
|
"code_samples": [],
|
|
"tables": [],
|
|
"images": [],
|
|
"channel": "help",
|
|
"date": "2024-04-02",
|
|
"message_count": 1,
|
|
},
|
|
{
|
|
"section_number": 3,
|
|
"heading": "#help - 2024-04-03",
|
|
"heading_level": "h2",
|
|
"text": "**dave** (2024-04-03T08:00:00): Happy Friday everyone!",
|
|
"headings": [],
|
|
"code_samples": [],
|
|
"tables": [],
|
|
"images": [],
|
|
"channel": "help",
|
|
"date": "2024-04-03",
|
|
"message_count": 1,
|
|
},
|
|
]
|
|
|
|
TOPIC_DATA = {
|
|
"source": "fixtures/discord-export.json",
|
|
"platform": "discord",
|
|
"metadata": {
|
|
"total_messages": 3,
|
|
"total_threads": 0,
|
|
"total_code_snippets": 0,
|
|
"total_links": 0,
|
|
"unique_users": 2,
|
|
"channels": ["help"],
|
|
},
|
|
"total_sections": 3,
|
|
"total_code_blocks": 0,
|
|
"channel_summaries": {
|
|
"help": {
|
|
"message_count": 3,
|
|
"unique_users": 2,
|
|
"date_range": {
|
|
"earliest": "2024-04-01T08:00:00",
|
|
"latest": "2024-04-03T08:00:00",
|
|
},
|
|
"top_users": [
|
|
{"user": "dave", "count": 2},
|
|
{"user": "erin", "count": 1},
|
|
],
|
|
"has_code": False,
|
|
},
|
|
},
|
|
"code_snippets": [],
|
|
"links": [],
|
|
"pages": TOPIC_SECTIONS,
|
|
}
|
|
|
|
# ── Single-category fixture — no topic matches → general → main.md ──────────
|
|
|
|
SINGLE_CATEGORY_DATA = {
|
|
"source": "fixtures/slack-export/",
|
|
"platform": "slack",
|
|
"metadata": {
|
|
"total_messages": 1,
|
|
"total_threads": 0,
|
|
"total_code_snippets": 0,
|
|
"total_links": 0,
|
|
"unique_users": 1,
|
|
"channels": ["general"],
|
|
},
|
|
"total_sections": 1,
|
|
"total_code_blocks": 0,
|
|
"channel_summaries": {
|
|
"general": {
|
|
"message_count": 1,
|
|
"unique_users": 1,
|
|
"date_range": {
|
|
"earliest": "2024-05-01T10:00:00",
|
|
"latest": "2024-05-01T10:00:00",
|
|
},
|
|
"top_users": [{"user": "frank", "count": 1}],
|
|
"has_code": False,
|
|
},
|
|
},
|
|
"code_snippets": [],
|
|
"links": [],
|
|
"pages": [
|
|
{
|
|
"section_number": 1,
|
|
"heading": "#general - 2024-05-01",
|
|
"heading_level": "h2",
|
|
"text": "**frank** (2024-05-01T10:00:00): Welcome aboard!",
|
|
"headings": [],
|
|
"code_samples": [],
|
|
"tables": [],
|
|
"images": [],
|
|
"channel": "general",
|
|
"date": "2024-05-01",
|
|
"message_count": 1,
|
|
},
|
|
],
|
|
}
|
|
|
|
# ── Empty fixture — no messages extracted ────────────────────────────────────
|
|
|
|
EMPTY_DATA = {
|
|
"source": "fixtures/empty-export/",
|
|
"platform": "slack",
|
|
"metadata": {
|
|
"total_messages": 0,
|
|
"total_threads": 0,
|
|
"total_code_snippets": 0,
|
|
"total_links": 0,
|
|
"unique_users": 0,
|
|
"channels": [],
|
|
},
|
|
"total_sections": 0,
|
|
"total_code_blocks": 0,
|
|
"channel_summaries": {},
|
|
"code_snippets": [],
|
|
"links": [],
|
|
"pages": [],
|
|
}
|
|
|
|
|
|
def _make_converter(tmp_path, name, platform, extracted):
|
|
from skill_seekers.cli.chat_scraper import ChatToSkillConverter
|
|
|
|
converter = ChatToSkillConverter(
|
|
{
|
|
"name": name,
|
|
"description": f"Use when testing the {name} golden build",
|
|
"platform": platform,
|
|
"output_dir": str(tmp_path / "skill"),
|
|
}
|
|
)
|
|
converter.extracted_data = copy.deepcopy(extracted)
|
|
return converter
|
|
|
|
|
|
def test_chat_multi_channel_matches_golden(tmp_path):
|
|
"""Two channels → channel-name categories with {key}_sN-sM.md filenames."""
|
|
converter = _make_converter(tmp_path, "golden_chat", "slack", MULTI_CHANNEL_DATA)
|
|
assert_matches_golden(build_snapshot(converter), "chat")
|
|
|
|
|
|
def test_chat_topic_buckets_match_golden(tmp_path):
|
|
"""Single channel → topic-bucket categories plus General Discussion."""
|
|
converter = _make_converter(tmp_path, "golden_chat_topics", "discord", TOPIC_DATA)
|
|
assert_matches_golden(build_snapshot(converter), "chat_topics")
|
|
|
|
|
|
def test_chat_single_category_matches_golden(tmp_path):
|
|
"""No topic matches → one 'general' category → main.md fast path."""
|
|
converter = _make_converter(tmp_path, "golden_chat_single", "slack", SINGLE_CATEGORY_DATA)
|
|
assert_matches_golden(build_snapshot(converter), "chat_single")
|
|
|
|
|
|
def test_chat_empty_matches_golden(tmp_path):
|
|
"""Empty export → 'content' fallback category and section_01.md filename."""
|
|
converter = _make_converter(tmp_path, "golden_chat_empty", "slack", EMPTY_DATA)
|
|
assert_matches_golden(build_snapshot(converter), "chat_empty")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("name", "platform", "data"),
|
|
[
|
|
("golden_chat", "slack", MULTI_CHANNEL_DATA),
|
|
("golden_chat_topics", "discord", TOPIC_DATA),
|
|
("golden_chat_single", "slack", SINGLE_CATEGORY_DATA),
|
|
("golden_chat_empty", "slack", EMPTY_DATA),
|
|
],
|
|
)
|
|
def test_skill_md_reference_links_point_at_real_files(tmp_path, name, platform, data):
|
|
"""Every `references/...` link in SKILL.md must name a file that was
|
|
actually written — the nav used to derive links from sanitized titles
|
|
while the writer named files via _reference_filename (DOC-07 drift)."""
|
|
converter = _make_converter(tmp_path, name, platform, data)
|
|
snapshot = build_snapshot(converter)
|
|
|
|
skill_md = snapshot["SKILL.md"].decode("utf-8")
|
|
links = re.findall(r"`(references/[^`]+)`", skill_md)
|
|
assert links, "SKILL.md should link at least references/index.md"
|
|
missing = [link for link in links if link not in snapshot]
|
|
assert not missing, f"SKILL.md links to nonexistent files: {missing}"
|