feat: support one Gitea organization per series

This commit is contained in:
2026-07-19 16:40:05 +02:00
parent 4dda6de387
commit b0548f0e56
6 changed files with 388 additions and 58 deletions
+122
View File
@@ -175,6 +175,128 @@ class StemctlContractTests(unittest.TestCase):
self.assertIn("runtime\tFAIL\tdocker is not supported", rendered)
self.assertIn("rootless\tFAIL\trootless Podman is required", rendered)
def test_manifest_is_authoritative_and_maps_logical_series_directory(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
workspace = root / "workspace"
info = workspace / "meta" / "workspace-info"
(info / "series").mkdir(parents=True)
physical_series = workspace / "series" / "freertos"
(physical_series / "card-one").mkdir(parents=True)
(physical_series / "unregistered-extra").mkdir()
(workspace / "series" / ".hidden").mkdir()
source_series = root / "original" / "series" / "freertos"
(source_series / "card-one").mkdir(parents=True)
(info / "series" / "freertos-c.json").write_text(
json.dumps(
{
"id": "freertos-c",
"source_org": "edu-freertos-c",
"workspace_dir": "freertos",
"source_dir": "freertos",
"cards": [{"id": "FC01", "repo": "card-one", "title": "First card"}],
}
),
encoding="utf-8",
)
(info / "workspace.json").write_text(
json.dumps(
{
"organizations": [
{"id": "edu", "role": "control-plane"},
{"id": "edu-freertos-c", "role": "series", "series": ["freertos-c"]},
],
"series": [{"id": "freertos-c", "file": "series/freertos-c.json"}],
}
),
encoding="utf-8",
)
config_path = root / "config.json"
config_path.write_text(
json.dumps(
{
"workspace_root": str(workspace),
"original_root": str(root / "original"),
"original_series_root": str(root / "original" / "series"),
}
),
encoding="utf-8",
)
config = rvctl.load_config(config_path)
series_output = io.StringIO()
cards_output = io.StringIO()
with redirect_stdout(series_output):
rvctl.print_series(config)
with redirect_stdout(cards_output):
rvctl.print_cards(config, "freertos-c")
self.assertIn("freertos-c\t1\t1", series_output.getvalue())
self.assertNotIn(".hidden", series_output.getvalue())
self.assertIn("FC01\tcard-one\tworkspace\tFirst card", cards_output.getvalue())
self.assertNotIn("unregistered-extra", cards_output.getvalue())
self.assertEqual(rvctl.workspace_series_path(config, "freertos-c"), physical_series)
self.assertEqual(rvctl.source_series_path(config, "freertos-c"), source_series)
self.assertEqual(rvctl.resolve_card_info(config, "freertos-c", "FC01").repo, "card-one")
def test_workspace_catalog_audit_accepts_registered_series_org(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
workspace = root / "workspace"
info = workspace / "meta" / "workspace-info"
info.mkdir(parents=True)
(info / "workspace.json").write_text(
json.dumps(
{
"organizations": [
{"id": "edu", "role": "control-plane"},
{"id": "edu-freertos-c", "role": "series", "series": ["freertos-c"]},
],
"series": [
{
"id": "freertos-c",
"source_org": "edu-freertos-c",
"cards": [{"id": "FC01", "repo": "card-one"}],
}
],
}
),
encoding="utf-8",
)
config_path = root / "config.json"
config_path.write_text(
json.dumps({"workspace_root": str(workspace), "original_root": str(root / "original")}),
encoding="utf-8",
)
config = rvctl.load_config(config_path)
checks = rvctl.audit_workspace_catalog(config)
self.assertFalse(any(status == "FAIL" for _, status, _ in checks), checks)
self.assertIn(("series:freertos-c", "ok", "source_org=edu-freertos-c"), checks)
def test_workspace_catalog_audit_rejects_implicit_source_org(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
workspace = root / "workspace"
info = workspace / "meta" / "workspace-info"
info.mkdir(parents=True)
(info / "workspace.json").write_text(
json.dumps(
{
"organizations": [{"id": "edu", "role": "control-plane"}],
"series": [{"id": "freertos-c", "cards": [{"id": "FC01", "repo": "card-one"}]}],
}
),
encoding="utf-8",
)
config_path = root / "config.json"
config_path.write_text(
json.dumps({"workspace_root": str(workspace), "original_root": str(root / "original")}),
encoding="utf-8",
)
config = rvctl.load_config(config_path)
checks = rvctl.audit_workspace_catalog(config)
self.assertIn(("series:freertos-c", "FAIL", "missing explicit source_org"), checks)
def test_existing_legacy_workspace_is_detected(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)