163 lines
5.8 KiB
Python
Executable File
163 lines
5.8 KiB
Python
Executable File
import base64
|
|
import tempfile
|
|
import unittest
|
|
from html.parser import HTMLParser
|
|
from pathlib import Path
|
|
|
|
from build_dual_html import (
|
|
EXPERIMENT_OUTPUT,
|
|
MANUSCRIPT_OUTPUT,
|
|
ROOT,
|
|
build_experiment_plan,
|
|
build_manuscript_reader,
|
|
document_shell,
|
|
figure_card,
|
|
image_data_uri,
|
|
inline_markup,
|
|
markdown_blocks,
|
|
markdown_table,
|
|
)
|
|
|
|
|
|
class CountingParser(HTMLParser):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.ids = []
|
|
self.images = 0
|
|
self.tables = 0
|
|
|
|
def handle_starttag(self, tag, attrs):
|
|
values = dict(attrs)
|
|
if "id" in values:
|
|
self.ids.append(values["id"])
|
|
if tag == "img":
|
|
self.images += 1
|
|
if tag == "table":
|
|
self.tables += 1
|
|
|
|
|
|
class MarkdownPrimitiveTests(unittest.TestCase):
|
|
def test_inline_markup_escapes_and_formats(self):
|
|
source = "**PlantAI** uses `Manifest` & [AIIA](https://example.org)."
|
|
rendered = inline_markup(source)
|
|
self.assertIn("<strong>PlantAI</strong>", rendered)
|
|
self.assertIn("<code>Manifest</code>", rendered)
|
|
self.assertIn("&", rendered)
|
|
self.assertIn('href="https://example.org"', rendered)
|
|
|
|
def test_markdown_table_is_semantic(self):
|
|
source = "| A | B |\n|---|---|\n| x | y |\n"
|
|
rendered = markdown_table(source)
|
|
self.assertIn("<thead>", rendered)
|
|
self.assertIn("<tbody>", rendered)
|
|
self.assertIn("<th>A</th>", rendered)
|
|
self.assertIn("<td>y</td>", rendered)
|
|
|
|
def test_markdown_blocks_supports_heading_list_quote_and_paragraph(self):
|
|
source = "## 标题\n\n> 提示\n\n- 项目一\n- 项目二\n\n正文。\n"
|
|
rendered = markdown_blocks(source)
|
|
self.assertIn("<h2", rendered)
|
|
self.assertIn("<blockquote>", rendered)
|
|
self.assertIn("<ul>", rendered)
|
|
self.assertIn("<p>正文。</p>", rendered)
|
|
|
|
def test_png_is_embedded_as_data_uri(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "plot.png"
|
|
payload = b"\x89PNG\r\n\x1a\nexample"
|
|
path.write_bytes(payload)
|
|
uri = image_data_uri(path)
|
|
self.assertEqual(
|
|
uri,
|
|
"data:image/png;base64," + base64.b64encode(payload).decode("ascii"),
|
|
)
|
|
|
|
|
|
class ManuscriptReaderTests(unittest.TestCase):
|
|
def test_document_shell_is_standalone(self):
|
|
page = document_shell(
|
|
"T", "E", "S", ["P"], [("x", "X")], ['<section id="x">X</section>']
|
|
)
|
|
self.assertIn("<!doctype html>", page.lower())
|
|
self.assertIn("@media print", page)
|
|
self.assertNotRegex(page, r'<(?:link|script)[^>]+(?:href|src)="https?://')
|
|
|
|
def test_simulated_figure_card_has_visible_warning(self):
|
|
png = ROOT / "figures/exports/figure2_rnaseq_scenarios_SIMULATED.png"
|
|
card = figure_card(2, "RNA-seq", "caption", png, True)
|
|
self.assertIn("SIMULATED DATA — DRAFT ONLY", card)
|
|
self.assertIn("data:image/png;base64,", card)
|
|
|
|
def test_manuscript_reader_contains_five_figures_and_three_tables(self):
|
|
page = build_manuscript_reader()
|
|
parser = CountingParser()
|
|
parser.feed(page)
|
|
self.assertEqual(parser.images, 5)
|
|
self.assertGreaterEqual(parser.tables, 3)
|
|
self.assertIn("PlantAI-dev", page)
|
|
self.assertIn("PlantAI-RAG", page)
|
|
self.assertGreaterEqual(page.count("SIMULATED DATA — DRAFT ONLY"), 8)
|
|
|
|
|
|
class ExperimentPlanTests(unittest.TestCase):
|
|
def test_experiment_plan_has_17_numbered_sections_and_all_assets(self):
|
|
page = build_experiment_plan()
|
|
parser = CountingParser()
|
|
parser.feed(page)
|
|
for number in range(17):
|
|
self.assertIn(f'id="plan-{number}"', page)
|
|
self.assertEqual(parser.images, 5)
|
|
self.assertGreaterEqual(parser.tables, 3)
|
|
self.assertGreaterEqual(page.count("SIMULATED DATA — DRAFT ONLY"), 8)
|
|
|
|
def test_experiment_plan_contains_five_experiments_and_reviewer_views(self):
|
|
page = build_experiment_plan()
|
|
for phrase in (
|
|
"系统级可执行闭环",
|
|
"多场景RNA-seq",
|
|
"RNA-seq到候选家族",
|
|
"轻量Extension",
|
|
"Graph RAG",
|
|
"审稿人视角",
|
|
"生信分析师视角",
|
|
"智能体工程师视角",
|
|
):
|
|
self.assertIn(phrase, page)
|
|
|
|
def test_experiment_plan_does_not_claim_excluded_capabilities(self):
|
|
page = build_experiment_plan()
|
|
self.assertIn("Diagnosis Agent不纳入论文贡献", page)
|
|
self.assertIn("agentic RAG未进入当前主路径", page)
|
|
self.assertIn("重测序—GWAS仅作为后续方向", page)
|
|
|
|
|
|
class OutputIntegrityTests(unittest.TestCase):
|
|
def test_both_outputs_are_self_contained_and_parseable(self):
|
|
for path in (MANUSCRIPT_OUTPUT, EXPERIMENT_OUTPUT):
|
|
page = path.read_text(encoding="utf-8")
|
|
parser = CountingParser()
|
|
parser.feed(page)
|
|
self.assertEqual(parser.images, 5)
|
|
self.assertEqual(len(parser.ids), len(set(parser.ids)))
|
|
self.assertNotIn("file://", page)
|
|
self.assertNotRegex(page, r'<img[^>]+src=["\'](?:/|https?://)')
|
|
self.assertNotRegex(page, r"\{\{[A-Z_][A-Z0-9_]*\}\}")
|
|
self.assertIn("@media print", page)
|
|
|
|
def test_required_terms_and_simulation_labels_are_present(self):
|
|
for path in (MANUSCRIPT_OUTPUT, EXPERIMENT_OUTPUT):
|
|
page = path.read_text(encoding="utf-8")
|
|
for term in (
|
|
"Artificial Intelligence in Agriculture",
|
|
"PlantAI-dev",
|
|
"PlantAI-RAG",
|
|
"Graph RAG",
|
|
"Extension",
|
|
"SIMULATED DATA — DRAFT ONLY",
|
|
):
|
|
self.assertIn(term, page)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|