Block mismatched selective WebUI releases
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -155,6 +156,78 @@ class ReleaseRepositoryTagTests(unittest.TestCase):
|
||||
for repository in (self.repo, self.remote, access, access_remote):
|
||||
self.assertFalse(ref_exists(repository, "refs/tags/v0.1.10"))
|
||||
|
||||
def test_batch_preflight_blocks_selected_webui_version_missing_from_core_bundle(self) -> None:
|
||||
campaign, campaign_remote = create_release_repo(
|
||||
root=self.root,
|
||||
workspace=self.workspace,
|
||||
name="govoplan-campaign",
|
||||
version="0.1.10",
|
||||
)
|
||||
campaign_webui = campaign / "webui"
|
||||
campaign_webui.mkdir()
|
||||
(campaign_webui / "package.json").write_text(
|
||||
'{"name":"@govoplan/campaign-webui","version":"0.1.10"}\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
git(campaign, "add", "webui/package.json")
|
||||
git(campaign, "commit", "-m", "Add Campaign WebUI package")
|
||||
git(campaign, "tag", "-a", "v0.1.9", "-m", "Prior Campaign release")
|
||||
git(campaign, "push", "origin", "main", "refs/tags/v0.1.9")
|
||||
campaign_head = git_text(campaign, "rev-parse", "HEAD")
|
||||
|
||||
core_webui = self.repo / "webui"
|
||||
core_webui.mkdir()
|
||||
old_ref = "git+ssh://git@example.test/acme/govoplan-campaign.git#v0.1.9"
|
||||
(core_webui / "package.release.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"name": "@govoplan/core-webui",
|
||||
"version": "0.1.10",
|
||||
"dependencies": {"@govoplan/campaign-webui": old_ref},
|
||||
}
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(core_webui / "package-lock.release.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@govoplan/core-webui",
|
||||
"version": "0.1.10",
|
||||
"dependencies": {"@govoplan/campaign-webui": old_ref},
|
||||
},
|
||||
"node_modules/@govoplan/campaign-webui": {
|
||||
"version": "0.1.9",
|
||||
"resolved": f"git+ssh://git@example.test/acme/govoplan-campaign.git#{campaign_head}",
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
git(self.repo, "add", "webui/package.release.json", "webui/package-lock.release.json")
|
||||
git(self.repo, "commit", "-m", "Add release WebUI composition")
|
||||
git(self.repo, "push", "origin", "main")
|
||||
|
||||
result = tag_repositories(
|
||||
repos=("govoplan-core", "govoplan-campaign"),
|
||||
repo_versions={"govoplan-core": "0.1.10", "govoplan-campaign": "0.1.10"},
|
||||
workspace_root=self.workspace,
|
||||
apply=True,
|
||||
push=True,
|
||||
)
|
||||
|
||||
self.assertEqual("blocked", result["status"])
|
||||
self.assertIn("no selected repository was mutated", result["detail"])
|
||||
self.assertEqual("skipped", result["repositories"][0]["status"])
|
||||
self.assertEqual("blocked", result["repositories"][1]["status"])
|
||||
self.assertIn("release WebUI composition gate failed", result["repositories"][1]["detail"])
|
||||
for repository in (self.repo, self.remote, campaign, campaign_remote):
|
||||
self.assertFalse(ref_exists(repository, "refs/tags/v0.1.10"))
|
||||
|
||||
def test_api_requires_explicit_confirmation_and_ui_exposes_source_release(self) -> None:
|
||||
with TestClient(create_app(workspace_root=self.workspace)) as client:
|
||||
rejected = client.post(
|
||||
|
||||
@@ -16,11 +16,19 @@ if str(RELEASE_TOOLS_ROOT) not in sys.path:
|
||||
from govoplan_release.version_alignment import ( # noqa: E402
|
||||
candidate_catalog_version_issues,
|
||||
release_composition_issues,
|
||||
selected_release_webui_bundle_issues,
|
||||
repository_version_issues,
|
||||
selected_repository_version_issues,
|
||||
)
|
||||
from govoplan_release.model import RepositorySnapshot, RepositorySpec, VersionSnapshot # noqa: E402
|
||||
from govoplan_release.selective_planner import build_unit # noqa: E402
|
||||
from govoplan_release.model import ( # noqa: E402
|
||||
CatalogSnapshot,
|
||||
DashboardSummary,
|
||||
ReleaseDashboard,
|
||||
RepositorySnapshot,
|
||||
RepositorySpec,
|
||||
VersionSnapshot,
|
||||
)
|
||||
from govoplan_release.selective_planner import build_selective_release_plan, build_unit # noqa: E402
|
||||
from govoplan_release.selective_catalog import enforce_selected_version_alignment # noqa: E402
|
||||
|
||||
|
||||
@@ -82,6 +90,168 @@ class VersionAlignmentTests(unittest.TestCase):
|
||||
workspace=workspace,
|
||||
)
|
||||
|
||||
def test_selected_webui_release_must_match_core_bundle_input_and_lock(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
workspace = Path(tmp)
|
||||
core_webui = workspace / "govoplan-core" / "webui"
|
||||
campaign_webui = workspace / "govoplan-campaign" / "webui"
|
||||
core_webui.mkdir(parents=True)
|
||||
campaign_webui.mkdir(parents=True)
|
||||
dependency = "@govoplan/campaign-webui"
|
||||
old_ref = "git+ssh://git@example.test/acme/govoplan-campaign.git#v1.2.2"
|
||||
(campaign_webui / "package.json").write_text(
|
||||
json.dumps({"name": dependency, "version": "1.2.3"})
|
||||
)
|
||||
(core_webui / "package.release.json").write_text(
|
||||
json.dumps({"dependencies": {dependency: old_ref}})
|
||||
)
|
||||
(core_webui / "package-lock.release.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"packages": {
|
||||
"": {"dependencies": {dependency: old_ref}},
|
||||
f"node_modules/{dependency}": {"version": "1.2.2"},
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
issues = selected_release_webui_bundle_issues(
|
||||
repo_versions={"govoplan-core": "2.0.0", "govoplan-campaign": "1.2.3"},
|
||||
workspace=workspace,
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "Core release WebUI dependency"):
|
||||
enforce_selected_version_alignment(
|
||||
repo_versions={"govoplan-campaign": "1.2.3"},
|
||||
workspace=workspace,
|
||||
)
|
||||
|
||||
self.assertEqual(2, len(issues))
|
||||
self.assertTrue(all(issue.repo == "govoplan-campaign" for issue in issues))
|
||||
self.assertEqual(
|
||||
{
|
||||
"Core release WebUI dependency must match the selected module version",
|
||||
"Core release lock must resolve the selected module version",
|
||||
},
|
||||
{issue.message for issue in issues},
|
||||
)
|
||||
|
||||
def test_selected_webui_gate_ignores_unselected_module_pins(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
workspace = Path(tmp)
|
||||
core_webui = workspace / "govoplan-core" / "webui"
|
||||
campaign_webui = workspace / "govoplan-campaign" / "webui"
|
||||
core_webui.mkdir(parents=True)
|
||||
campaign_webui.mkdir(parents=True)
|
||||
campaign_package = "@govoplan/campaign-webui"
|
||||
files_package = "@govoplan/files-webui"
|
||||
campaign_ref = "git+ssh://git@example.test/acme/govoplan-campaign.git#v1.2.3"
|
||||
files_ref = "git+ssh://git@example.test/acme/govoplan-files.git#v0.9.0"
|
||||
(campaign_webui / "package.json").write_text(
|
||||
json.dumps({"name": campaign_package, "version": "1.2.3"})
|
||||
)
|
||||
dependencies = {campaign_package: campaign_ref, files_package: files_ref}
|
||||
(core_webui / "package.release.json").write_text(json.dumps({"dependencies": dependencies}))
|
||||
(core_webui / "package-lock.release.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"packages": {
|
||||
"": {"dependencies": dependencies},
|
||||
f"node_modules/{campaign_package}": {"version": "1.2.3"},
|
||||
f"node_modules/{files_package}": {"version": "0.9.0"},
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
issues = selected_release_webui_bundle_issues(
|
||||
repo_versions={"govoplan-campaign": "1.2.3"},
|
||||
workspace=workspace,
|
||||
)
|
||||
|
||||
self.assertEqual((), issues)
|
||||
|
||||
def test_selective_plan_blocks_mismatched_selected_webui_bundle(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
workspace = Path(tmp)
|
||||
core_webui = workspace / "govoplan-core" / "webui"
|
||||
campaign_webui = workspace / "govoplan-campaign" / "webui"
|
||||
core_webui.mkdir(parents=True)
|
||||
campaign_webui.mkdir(parents=True)
|
||||
package_name = "@govoplan/campaign-webui"
|
||||
old_ref = "git+ssh://git@example.test/acme/govoplan-campaign.git#v1.2.2"
|
||||
(campaign_webui / "package.json").write_text(
|
||||
json.dumps({"name": package_name, "version": "1.2.3"})
|
||||
)
|
||||
(core_webui / "package.release.json").write_text(
|
||||
json.dumps({"dependencies": {package_name: old_ref}})
|
||||
)
|
||||
(core_webui / "package-lock.release.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"packages": {
|
||||
"": {"dependencies": {package_name: old_ref}},
|
||||
f"node_modules/{package_name}": {"version": "1.2.2"},
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
campaign = RepositorySnapshot(
|
||||
spec=RepositorySpec(
|
||||
name="govoplan-campaign",
|
||||
category="module",
|
||||
subtype="domain",
|
||||
remote="git@example.test:acme/govoplan-campaign.git",
|
||||
path="govoplan-campaign",
|
||||
),
|
||||
absolute_path=str(workspace / "govoplan-campaign"),
|
||||
exists=True,
|
||||
is_git=True,
|
||||
has_head=True,
|
||||
branch="main",
|
||||
versions=VersionSnapshot(pyproject="1.2.3", webui_package="1.2.3"),
|
||||
)
|
||||
dashboard = ReleaseDashboard(
|
||||
generated_at="2026-01-01T00:00:00Z",
|
||||
meta_root=str(workspace / "govoplan"),
|
||||
workspace_root=str(workspace),
|
||||
target_version=None,
|
||||
target_tag=None,
|
||||
online=False,
|
||||
include_migrations=False,
|
||||
summary=DashboardSummary(
|
||||
repository_count=1,
|
||||
missing_count=0,
|
||||
dirty_count=0,
|
||||
ahead_count=0,
|
||||
behind_count=0,
|
||||
no_head_count=0,
|
||||
error_count=0,
|
||||
safe_directory_count=0,
|
||||
local_target_tag_missing_count=0,
|
||||
status="ready",
|
||||
),
|
||||
repositories=(campaign,),
|
||||
catalog=CatalogSnapshot(
|
||||
channel="stable",
|
||||
website_path="",
|
||||
catalog_path="",
|
||||
catalog_exists=False,
|
||||
keyring_path="",
|
||||
keyring_exists=False,
|
||||
),
|
||||
)
|
||||
|
||||
plan = build_selective_release_plan(
|
||||
dashboard,
|
||||
selected_repos=("govoplan-campaign",),
|
||||
repo_versions={"govoplan-campaign": "1.2.3"},
|
||||
)
|
||||
|
||||
self.assertEqual("blocked", plan.status)
|
||||
self.assertEqual("blocked", plan.units[0].status)
|
||||
self.assertTrue(any("Core release WebUI dependency" in item for item in plan.units[0].blockers))
|
||||
|
||||
def test_selected_repository_gate_reports_missing_version_metadata(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
workspace = Path(tmp)
|
||||
|
||||
Reference in New Issue
Block a user