import hashlib import json import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] WIDGETS = { "active-projects": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "active-projects" / "v1", "documents": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "documents" / "v1", "tasks": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "tasks" / "v1", "finance": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "finance" / "v1", "crm": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "crm" / "v1", "discussions": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "discussions" / "v1", "notifications": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "notifications" / "v1", "scheduling": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "scheduling" / "v1", "inventory": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "inventory" / "v1", "agents": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "agents" / "v1", "status-page": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "status-page" / "v1", "project-db": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "project-db" / "v1", "notes": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "notes" / "v1", "project-overview": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "project-overview" / "v1", "files": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "files" / "v1", "proposals": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "proposals" / "v1", "terms": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "terms" / "v1", "activity": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "activity" / "v1", "organizations": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "organizations" / "v1", "gitea": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "gitea" / "v1", "schemes": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "schemes" / "v1", "task-kanban": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "task-kanban" / "v1", "team-workload": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "team-workload" / "v1", "project-timeline": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "project-timeline" / "v1", "crm-funnel": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "crm-funnel" / "v1", "finance-dashboard": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "finance-dashboard" / "v1", "proposal-approvals": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "proposal-approvals" / "v1", "documents-completeness": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "documents-completeness" / "v1", "agents-health": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "agents-health" / "v1", "schedule-calendar": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "schedule-calendar" / "v1", "acl-matrix": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "acl-matrix" / "v1", "inventory-warnings": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "inventory-warnings" / "v1", "gitea-board": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "gitea-board" / "v1", "schemes-progress": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "schemes-progress" / "v1", "notes-tree": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "notes-tree" / "v1", "project-db-preview": ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "project-db-preview" / "v1", } class WidgetArtifactTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.artifacts = {} for name, widget in WIDGETS.items(): manifest = json.loads((widget / "manifest.json").read_text(encoding="utf-8")) resource = next(item for item in manifest["resources"] if item["file"] == "index.html") cls.artifacts[name] = { "widget": widget, "manifest": manifest, "resource": resource, "html": (widget / "index.html").read_text(encoding="utf-8"), } def test_manifest_and_checksums_match_artifact(self): for name, artifact in self.artifacts.items(): widget = artifact["widget"] manifest = artifact["manifest"] resource = artifact["resource"] html = artifact["html"] digest = hashlib.sha256(html.encode("utf-8")).hexdigest() self.assertEqual(resource["uri"], f"ui://shd/{name}/v1.html", name) self.assertEqual(resource["mediaType"], "text/html;profile=mcp-app", name) self.assertEqual(resource["bytes"], len(html.encode("utf-8")), name) self.assertEqual(resource["sha256"], digest, name) checksum = (widget / "SHA256SUMS").read_text(encoding="utf-8").strip().split() self.assertEqual(checksum, [digest, "index.html"], name) license_path = (widget / manifest["license"]).resolve() self.assertEqual(license_path, (ROOT / "LICENSE").resolve(), name) source_path = (ROOT / manifest["source"]["path"]).resolve() self.assertEqual(source_path, (widget / "index.html").resolve(), name) def test_widget_uses_portable_bridge_and_no_external_script(self): for name, artifact in self.artifacts.items(): for marker in ( "ui/initialize", "ui/notifications/initialized", "ui/notifications/tool-input", "ui/notifications/tool-result", "tools/call", "window.openai", ): self.assertIn(marker, artifact["html"], f"{name}: {marker}") self.assertNotIn("