# retoor import json import pytest from devplacepy.services.community.discoverer import CommunityDiscoverer from devplacepy.services.community.extract import extract_json_ld_posts, parse_html from devplacepy.services.community.generator import CodeGenerator from devplacepy.services.community.runtime import Collector from devplacepy.services.community.schemacheck import validate_schema from devplacepy.services.sync.records import RECORD_MODELS from tests.conftest import run_async from tests.unit.services.community.fakes import FakeFetcher from tests.unit.services.community.generator import load, no_sleep def thread(number: int, comments: int = 2) -> dict: return { "@type": "DiscussionForumPosting", "@id": f"https://ld.test/t/{number}", "headline": f"Thread number {number} about parsing", "text": f"This is the body of thread {number}, written long enough to sync as a post.", "author": {"@type": "Person", "name": f"writer{number % 2}", "url": f"https://ld.test/u/{number % 2}"}, "datePublished": f"2026-10-{number:02d}T10:00:00Z", "dateModified": f"2026-10-{number:02d}T12:00:00Z", "url": f"https://ld.test/t/{number}", "comment": [ { "@type": "Comment", "@id": f"https://ld.test/t/{number}#c{index}", "text": f"Comment {index} on thread {number}", "author": {"@type": "Person", "name": f"replier{index}"}, "dateCreated": f"2026-10-{number:02d}T11:0{index}:00Z", } for index in range(comments) ], } def page(blobs: list) -> str: scripts = "".join(f'' for blob in blobs) return f"LD Forum{scripts}
Loading
" def ld_site(blobs=None) -> dict: blobs = blobs or [{"@context": "https://schema.org", "@graph": [thread(number) for number in range(1, 7)]}] return {"https://ld.test/": page(blobs), "https://ld.test/about": "

About: 100 members.

"} def test_the_extractor_normalizes_graphs_lists_and_nested_comments(): root = parse_html(page([{"@graph": [thread(1), {"@type": "WebSite", "name": "x"}]}, thread(2), [thread(3)], {"@type": "ItemList", "itemListElement": [{"item": thread(4)}]}])) posts = extract_json_ld_posts(root) assert [item["id"] for item in posts] == [f"https://ld.test/t/{n}" for n in (1, 2, 3, 4)] first = posts[0] assert first["title"] == "Thread number 1 about parsing" and first["author"] == {"username": "writer1", "id": "https://ld.test/u/1"} assert first["created_at"].startswith("2026-10-01") and len(first["comments"]) == 2 assert first["comments"][0]["content"] == "Comment 0 on thread 1" and first["comments"][0]["author"] == {"username": "replier0"} def test_garbage_and_non_posts_are_ignored(): root = parse_html('') assert extract_json_ld_posts(root) == [] nameless = parse_html(page([{"@type": "Article", "headline": "No identity at all"}])) assert extract_json_ld_posts(nameless) == [] def test_a_site_with_only_structured_data_is_learned_end_to_end(): result = run_async(CommunityDiscoverer(FakeFetcher(ld_site())).discover("https://ld.test/")) assert result.status == "ready", result.error feed = result.schema["feeds"][0] assert feed["type"] == "json_ld" and feed["post"]["fields"]["id"] and feed["comments"]["mode"] == "inline" assert validate_schema(result.schema) == [] records = result.sample["records"] assert len(records["posts"]) == 6 and len(records["comments"]) == 6 and records["users"] full = run_async(Collector(FakeFetcher(ld_site()), result.schema, sleep=no_sleep).run()) assert len(full["records"]["comments"]) == 12 for kind, rows in records.items(): for row in rows: RECORD_MODELS[kind].model_validate(row) assert {user["username"] for user in records["users"]} >= {"writer0", "writer1", "replier0"} def test_the_collector_and_the_generated_file_agree_on_structured_data(tmp_path): schema = run_async(CommunityDiscoverer(FakeFetcher(ld_site())).discover("https://ld.test/")).schema direct = run_async(Collector(FakeFetcher(ld_site()), schema, sleep=no_sleep).run()) module = load(CodeGenerator(schema).write(tmp_path)) generated = run_async(module.collect_all(FakeFetcher(ld_site()), sleep=no_sleep)) assert {kind: len(rows) for kind, rows in direct["records"].items()} == {kind: len(rows) for kind, rows in generated["records"].items()} def test_a_single_structured_post_is_not_a_feed(): result = run_async(CommunityDiscoverer(FakeFetcher(ld_site([thread(1)]))).discover("https://ld.test/")) assert result.status != "ready" @pytest.mark.parametrize("count", [2, 5]) def test_few_posts_still_make_a_feed(count): result = run_async(CommunityDiscoverer(FakeFetcher(ld_site([{"@graph": [thread(number, 0) for number in range(1, count + 1)]}]))).discover("https://ld.test/")) assert result.status == "ready", result.error