feat: add web UI project registry and onboarding (#427)
Load projects from versioned webui/data/projects.registry.json with profile
mappings, workflow/schema paths, and read-only onboarding checklist UI.
Seeds Gitea-Tools; exposes /projects, /projects/{id}, and /api/projects.
Closes #427
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
"""Tests for web UI project registry (#427)."""
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from webui.app import create_app
|
||||
from webui.project_registry import (
|
||||
default_registry_path,
|
||||
load_registry,
|
||||
project_to_dict,
|
||||
)
|
||||
|
||||
|
||||
class TestProjectRegistryLoader(unittest.TestCase):
|
||||
def test_default_registry_loads_gitea_tools(self):
|
||||
registry = load_registry()
|
||||
self.assertEqual(registry.version, 1)
|
||||
self.assertEqual(len(registry.projects), 1)
|
||||
project = registry.projects[0]
|
||||
self.assertEqual(project.id, "gitea-tools")
|
||||
self.assertEqual(project.repo_name, "Gitea-Tools")
|
||||
self.assertEqual(project.gitea_owner, "Scaled-Tech-Consulting")
|
||||
self.assertEqual(project.remote_host, "https://gitea.prgs.cc")
|
||||
self.assertEqual(project.profiles["author"], "prgs-author")
|
||||
self.assertEqual(project.profiles["reviewer"], "prgs-reviewer")
|
||||
self.assertEqual(project.profiles["reconciler"], "prgs-reconciler")
|
||||
self.assertIn("skill", project.workflow_paths)
|
||||
self.assertGreaterEqual(len(project.onboarding_checklist), 4)
|
||||
|
||||
def test_registry_rejects_credential_keys(self):
|
||||
payload = {
|
||||
"version": 1,
|
||||
"projects": [
|
||||
{
|
||||
"id": "bad",
|
||||
"repo_name": "Bad",
|
||||
"gitea_owner": "Org",
|
||||
"remote_host": "https://gitea.example.invalid",
|
||||
"default_branch": "main",
|
||||
"local_checkout_path": ".",
|
||||
"profiles": {
|
||||
"author": "a",
|
||||
"reviewer": "r",
|
||||
"reconciler": "c",
|
||||
},
|
||||
"workflow_paths": {"skill": "skills/x.md"},
|
||||
"api_token": "secret",
|
||||
}
|
||||
],
|
||||
}
|
||||
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as handle:
|
||||
json.dump(payload, handle)
|
||||
path = Path(handle.name)
|
||||
try:
|
||||
with self.assertRaises(ValueError):
|
||||
load_registry(path)
|
||||
finally:
|
||||
path.unlink(missing_ok=True)
|
||||
|
||||
def test_default_registry_path_points_at_packaged_data(self):
|
||||
path = default_registry_path()
|
||||
self.assertTrue(path.name == "projects.registry.json")
|
||||
self.assertTrue(path.parent.name == "data")
|
||||
|
||||
|
||||
class TestProjectRegistryRoutes(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = TestClient(create_app())
|
||||
|
||||
def test_projects_page_lists_gitea_tools(self):
|
||||
response = self.client.get("/projects")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Gitea-Tools", response.text)
|
||||
self.assertIn("Scaled-Tech-Consulting", response.text)
|
||||
self.assertIn("prgs-author", response.text)
|
||||
self.assertNotIn("child issue", response.text.lower())
|
||||
|
||||
def test_project_detail_renders_checklist(self):
|
||||
response = self.client.get("/projects/gitea-tools")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Onboarding checklist", response.text)
|
||||
self.assertIn("Configure execution profiles", response.text)
|
||||
self.assertIn("branches/", response.text)
|
||||
|
||||
def test_project_detail_404(self):
|
||||
response = self.client.get("/projects/unknown-repo")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_api_projects_json(self):
|
||||
response = self.client.get("/api/projects")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json()
|
||||
self.assertEqual(data["version"], 1)
|
||||
self.assertEqual(len(data["projects"]), 1)
|
||||
self.assertEqual(data["projects"][0]["id"], "gitea-tools")
|
||||
self.assertIn("onboarding_checklist", data["projects"][0])
|
||||
|
||||
def test_project_to_dict_is_json_safe(self):
|
||||
registry = load_registry()
|
||||
encoded = json.dumps(project_to_dict(registry.projects[0]))
|
||||
self.assertIn("gitea-tools", encoded)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -30,12 +30,17 @@ class TestWebuiSkeleton(unittest.TestCase):
|
||||
self.assertIn("Read-only MVP", response.text)
|
||||
|
||||
def test_route_stubs_render(self):
|
||||
for path in ("/projects", "/prompts", "/runtime", "/audit"):
|
||||
for path in ("/prompts", "/runtime", "/audit"):
|
||||
with self.subTest(path=path):
|
||||
response = self.client.get(path)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("child issue", response.text.lower())
|
||||
|
||||
def test_projects_is_implemented(self):
|
||||
response = self.client.get("/projects")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Gitea-Tools", response.text)
|
||||
|
||||
def test_extra_stub_routes(self):
|
||||
for path in ("/worktrees", "/leases"):
|
||||
with self.subTest(path=path):
|
||||
|
||||
Reference in New Issue
Block a user