327 lines
14 KiB
Python
327 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import unittest
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
import sys
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
sys.path.insert(0, str(ROOT / "scripts"))
|
|
|
|
import build
|
|
import translate_site
|
|
|
|
from scisiteforge.content import SiteContent, load_citegeist_cards, load_didactopus_cards, load_doclift_cards, load_groundrecall_cards
|
|
from scisiteforge.notebook import load_notebooks, render_notebooks
|
|
from scisiteforge.themes import get_theme, materialize_theme
|
|
from scisiteforge.translations import GenieHiveTranslator, TranslationConfig
|
|
|
|
|
|
class SciSiteForgeTests(unittest.TestCase):
|
|
def test_theme_materialization_copies_theme_assets(self) -> None:
|
|
from tempfile import TemporaryDirectory
|
|
|
|
with TemporaryDirectory() as tmp:
|
|
tmp_path = Path(tmp)
|
|
theme = get_theme("talkorigins-modern")
|
|
payload = materialize_theme(theme, tmp_path)
|
|
|
|
self.assertTrue((tmp_path / "theme" / "style.css").exists())
|
|
self.assertTrue((tmp_path / "theme" / "main.js").exists())
|
|
self.assertTrue((tmp_path / "theme" / "assets" / "toa.ico").exists())
|
|
self.assertTrue((tmp_path / "theme" / "assets" / "toa_logo_001_edit_001.png").exists())
|
|
self.assertEqual(payload["theme_name"], "talkorigins-modern")
|
|
|
|
def test_content_loaders_parse_local_repo_artifacts(self) -> None:
|
|
from tempfile import TemporaryDirectory
|
|
|
|
with TemporaryDirectory() as tmp:
|
|
tmp_path = Path(tmp)
|
|
|
|
doclift_root = tmp_path / "doclift"
|
|
doclift_root.mkdir()
|
|
(doclift_root / "manifest.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"documents": [
|
|
{
|
|
"document_id": "doc-1",
|
|
"title": "Legacy Document",
|
|
"document_kind": "article",
|
|
"markdown_path": "documents/doc-1/document.md",
|
|
"source_path": "source/doc-1.html",
|
|
}
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
(doclift_root / "documents" / "doc-1").mkdir(parents=True)
|
|
(doclift_root / "documents" / "doc-1" / "document.md").write_text("First paragraph.\n\nSecond.", encoding="utf-8")
|
|
|
|
groundrecall_root = tmp_path / "groundrecall"
|
|
groundrecall_root.mkdir()
|
|
(groundrecall_root / "groundrecall_query_bundle.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"concept": {"concept_id": "concept::topic", "title": "GroundRecall Topic"},
|
|
"summary": "Grounded summary.",
|
|
"claims": [{"claim_id": "clm-1", "claim_text": "Claim one", "claim_kind": "summary"}],
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
didactopus_root = tmp_path / "didactopus"
|
|
didactopus_root.mkdir()
|
|
(didactopus_root / "pack.yaml").write_text("name: test-pack\ndisplay_name: Test Pack\n", encoding="utf-8")
|
|
(didactopus_root / "concepts.yaml").write_text(
|
|
"concepts:\n - id: prior\n title: Prior\n description: Previous knowledge.\n prerequisites: []\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
citegeist_root = tmp_path / "citegeist"
|
|
citegeist_root.mkdir()
|
|
(citegeist_root / "refs.bib").write_text(
|
|
"""@article{smith2024,\n title = {A Study},\n author = {Smith, Jane and Roe, John},\n year = {2024}\n}\n""",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
doclift_cards = load_doclift_cards(doclift_root)
|
|
groundrecall_cards = load_groundrecall_cards(groundrecall_root)
|
|
didactopus_cards = load_didactopus_cards(didactopus_root)
|
|
citegeist_cards = load_citegeist_cards(citegeist_root)
|
|
|
|
self.assertEqual(doclift_cards[0].title, "Legacy Document")
|
|
self.assertEqual(doclift_cards[0].body, "First paragraph.")
|
|
self.assertEqual(groundrecall_cards[0].title, "GroundRecall Topic")
|
|
self.assertEqual(groundrecall_cards[1].title, "Claim one")
|
|
self.assertEqual(didactopus_cards[0].title, "Prior")
|
|
self.assertEqual(citegeist_cards[0].title, "A Study")
|
|
|
|
def test_build_site_renders_selected_theme_and_content(self) -> None:
|
|
from tempfile import TemporaryDirectory
|
|
|
|
with TemporaryDirectory() as tmp:
|
|
tmp_path = Path(tmp)
|
|
content_root = tmp_path / "content"
|
|
content_root.mkdir()
|
|
|
|
doclift_root = content_root / "doclift"
|
|
doclift_root.mkdir()
|
|
(doclift_root / "manifest.json").write_text(
|
|
json.dumps({"documents": [{"document_id": "doc-1", "title": "Legacy Document", "markdown_path": "documents/doc-1/document.md"}]}),
|
|
encoding="utf-8",
|
|
)
|
|
(doclift_root / "documents" / "doc-1").mkdir(parents=True)
|
|
(doclift_root / "documents" / "doc-1" / "document.md").write_text("Doclift content.", encoding="utf-8")
|
|
|
|
didactopus_root = content_root / "didactopus"
|
|
didactopus_root.mkdir()
|
|
(didactopus_root / "pack.yaml").write_text("name: test-pack\ndisplay_name: Test Pack\n", encoding="utf-8")
|
|
(didactopus_root / "concepts.yaml").write_text(
|
|
"concepts:\n - id: prior\n title: Prior\n description: Previous knowledge.\n prerequisites: []\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
groundrecall_root = content_root / "groundrecall"
|
|
groundrecall_root.mkdir()
|
|
(groundrecall_root / "groundrecall_query_bundle.json").write_text(
|
|
json.dumps({"concept": {"concept_id": "concept::topic", "title": "GroundRecall Topic"}, "summary": "Grounded summary."}),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
citegeist_root = content_root / "citegeist"
|
|
citegeist_root.mkdir()
|
|
(citegeist_root / "refs.bib").write_text(
|
|
"""@article{smith2024,\n title = {A Study},\n author = {Smith, Jane and Roe, John},\n year = {2024}\n}\n""",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
config = {
|
|
"lang": "en",
|
|
"title": "TalkOrigins Preview",
|
|
"site_title": "TalkOrigins Archive",
|
|
"license": "CC BY-SA 4.0",
|
|
"github_url": "https://example.invalid",
|
|
"contact_email": "admin@example.invalid",
|
|
"theme": "talkorigins-modern",
|
|
"languages": [{"code": "en", "name": "English"}],
|
|
"navigation": [{"label": "Home", "href": "/"}],
|
|
"hero": {
|
|
"kicker": "Archive Preview",
|
|
"title": "Modernized, reviewable, and still archive-first.",
|
|
"lede": "Proof-of-concept output from SciSiteForge.",
|
|
"actions": [{"label": "Open", "href": "#overview", "primary": True}],
|
|
},
|
|
"content_sources": {
|
|
"doclift_bundle": str(doclift_root),
|
|
"groundrecall_bundle": str(groundrecall_root),
|
|
"didactopus_pack": str(didactopus_root),
|
|
"bibliography": str(citegeist_root),
|
|
},
|
|
}
|
|
config_path = tmp_path / "site.json"
|
|
config_path.write_text(json.dumps(config), encoding="utf-8")
|
|
|
|
out_dir = tmp_path / "out"
|
|
result = build.build_site(config_path, out_dir)
|
|
|
|
html = (out_dir / "index.html").read_text(encoding="utf-8")
|
|
self.assertEqual(result["theme"], "talkorigins-modern")
|
|
self.assertIn("TalkOrigins Preview", html)
|
|
self.assertIn("Legacy Document", html)
|
|
self.assertIn("GroundRecall Topic", html)
|
|
self.assertIn("Prior", html)
|
|
self.assertIn("A Study", html)
|
|
self.assertTrue((out_dir / "theme" / "assets" / "toa.ico").exists())
|
|
|
|
def test_build_site_filters_languages_by_coverage_and_shows_planned_list(self) -> None:
|
|
from tempfile import TemporaryDirectory
|
|
|
|
with TemporaryDirectory() as tmp:
|
|
tmp_path = Path(tmp)
|
|
config = {
|
|
"lang": "en",
|
|
"title": "TalkOrigins Preview",
|
|
"site_title": "TalkOrigins Archive",
|
|
"license": "CC BY-SA 4.0",
|
|
"github_url": "https://example.invalid",
|
|
"contact_email": "admin@example.invalid",
|
|
"theme": "talkorigins-modern",
|
|
"languages": [
|
|
{"code": "en", "name": "English", "coverage": True},
|
|
{"code": "es", "name": "Español", "coverage": False},
|
|
{"code": "fr", "name": "Français", "coverage": False},
|
|
],
|
|
"language_policy": {
|
|
"planned_languages": [
|
|
{"code": "es", "name": "Español"},
|
|
{"code": "fr", "name": "Français"},
|
|
]
|
|
},
|
|
"navigation": [{"label": "Home", "href": "/"}],
|
|
"hero": {
|
|
"kicker": "Archive Preview",
|
|
"title": "Modernized, reviewable, and still archive-first.",
|
|
"lede": "Proof-of-concept output from SciSiteForge.",
|
|
"actions": [{"label": "Open", "href": "#overview", "primary": True}],
|
|
},
|
|
"content_sources": {},
|
|
}
|
|
config_path = tmp_path / "site.json"
|
|
config_path.write_text(json.dumps(config), encoding="utf-8")
|
|
|
|
out_dir = tmp_path / "out"
|
|
build.build_site(config_path, out_dir)
|
|
|
|
html = (out_dir / "index.html").read_text(encoding="utf-8")
|
|
self.assertIn('<option value="en" selected>English</option>', html)
|
|
self.assertNotIn('value="es"', html)
|
|
self.assertNotIn('value="fr"', html)
|
|
self.assertIn("Planned languages: Español, Français", html)
|
|
|
|
def test_notebook_pattern_groups_goals_apps_and_source_cards(self) -> None:
|
|
from tempfile import TemporaryDirectory
|
|
|
|
with TemporaryDirectory() as tmp:
|
|
tmp_path = Path(tmp)
|
|
doclift_root = tmp_path / "doclift"
|
|
doclift_root.mkdir()
|
|
(doclift_root / "manifest.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"documents": [
|
|
{
|
|
"document_id": "doc-1",
|
|
"title": "Legacy Reading",
|
|
"document_kind": "article",
|
|
"markdown_path": "documents/doc-1/document.md",
|
|
}
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
(doclift_root / "documents" / "doc-1").mkdir(parents=True)
|
|
(doclift_root / "documents" / "doc-1" / "document.md").write_text("Recovered source paragraph.", encoding="utf-8")
|
|
|
|
config = {
|
|
"notebooks": [
|
|
{
|
|
"id": "digital-evolution",
|
|
"title": "Digital Evolution Notebook",
|
|
"summary": "Lab plus source-grounded study path.",
|
|
"audience": "self-learners",
|
|
"goals": ["Connect simulation output to evolutionary concepts"],
|
|
"apps": [{"title": "Avida-ED", "href": "/app4/", "description": "Digital evolution lab"}],
|
|
"source_kinds": ["notebook"],
|
|
}
|
|
]
|
|
}
|
|
notebooks = load_notebooks(config)
|
|
content = SiteContent(section_cards=load_doclift_cards(doclift_root))
|
|
html = render_notebooks(notebooks, content)
|
|
|
|
self.assertIn("Digital Evolution Notebook", html)
|
|
self.assertIn("Avida-ED", html)
|
|
self.assertIn("Legacy Reading", html)
|
|
self.assertIn("Recovered source paragraph.", html)
|
|
|
|
def test_geniehive_translator_uses_openai_compatible_chat_payload(self) -> None:
|
|
translator = GenieHiveTranslator(
|
|
TranslationConfig(base_url="http://geniehive.local:8800", model="translation-role", api_key="abc123")
|
|
)
|
|
captured: dict[str, object] = {}
|
|
|
|
def fake_post_json(path: str, payload: dict) -> dict:
|
|
captured["path"] = path
|
|
captured["payload"] = payload
|
|
return {"choices": [{"message": {"content": "Hola"}}]}
|
|
|
|
translator._post_json = fake_post_json # type: ignore[method-assign]
|
|
result = translator.translate("Hello world", "Spanish", {"evolution": "evolución"})
|
|
|
|
self.assertEqual(result, "Hola")
|
|
self.assertEqual(captured["path"], "/v1/chat/completions")
|
|
payload = captured["payload"]
|
|
self.assertEqual(payload["model"], "translation-role")
|
|
user_text = payload["messages"][1]["content"]
|
|
self.assertIn("Spanish", user_text)
|
|
self.assertIn("evolución", user_text)
|
|
|
|
def test_translate_site_builds_translator_from_config(self) -> None:
|
|
from tempfile import TemporaryDirectory
|
|
|
|
with TemporaryDirectory() as tmp:
|
|
tmp_path = Path(tmp)
|
|
config_path = tmp_path / "site.json"
|
|
config_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"translation": {
|
|
"base_url": "http://geniehive.local:8800",
|
|
"model": "translation-role",
|
|
"api_key": "abc123",
|
|
"timeout": 33,
|
|
"system_prompt": "Translate carefully.",
|
|
}
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
args = SimpleNamespace(base_url=None, model=None, api_key=None, timeout=None)
|
|
translator = translate_site.build_translator(config_path, args)
|
|
|
|
self.assertEqual(translator.config.provider, "geniehive")
|
|
self.assertEqual(translator.config.base_url, "http://geniehive.local:8800")
|
|
self.assertEqual(translator.config.model, "translation-role")
|
|
self.assertEqual(translator.config.api_key, "abc123")
|
|
self.assertEqual(translator.config.timeout, 33)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|