"""Release gate checks use fixtures so the workflow can fail closed before publication."""
import tempfile
import unittest
from pathlib import Path
from release_gate import GateError, validate_poms, validate_published_poms, validate_smoke
RELEASE_SHA = "a" * 40
QBITS = (
"quick-search", "user-role-permissions", "customizable-table-views",
"standard-process-trace", "webhooks", "workflows", "geo-data",
"sftp-data-integration",
)
def management(qqq_version="${qqq.version}", qbit_version=None):
qbit_version = qbit_version or (lambda name: f"${{qbit-{name}.version}}")
dependencies = (
"com.kingsrook.qqqqqq-bom-pom"
f"{qqq_version}pomimport"
)
dependencies += "".join(
"com.kingsrook.qbits"
f"qbit-{name}{qbit_version(name)}"
for name in QBITS
)
dependencies += (
"com.kingsrook.qqqqqq-frontend-next"
"1.0.0-RC.8"
)
return f"{dependencies}"
class ReleaseGateTest(unittest.TestCase):
def setUp(self):
self.directory = tempfile.TemporaryDirectory()
self.addCleanup(self.directory.cleanup)
self.root = Path(self.directory.name)
(self.root / "qqq-all-bom").mkdir()
self.write_poms("4.1.0", "4.1.0", "1.2.3")
def write_poms(self, revision, qqq_version, qbit_version):
(self.root / "pom.xml").write_text(
f"{revision}"
)
(self.root / "qqq-all-bom/pom.xml").write_text(
""
f"{qqq_version}"
"1.0.0-RC.8"
+ "".join(f"{qbit_version}" for name in QBITS)
+ "" + management() + ""
)
def published_poms(self, resolved=False):
parent = self.root / "published-parent.pom"
bom = self.root / "published-bom.pom"
parent.write_text(""
"com.kingsrook.qqq"
"qqq-all-parent4.1.0")
managed = (management("4.1.0", lambda name: "1.2.3") if resolved else management())
bom.write_text(""
"com.kingsrook.qqq"
"qqq-all-parent4.1.0"
"qqq-all-bom4.1.0"
"1.0.0-RC.8"
+ "".join(f"1.2.3" for name in QBITS)
+ "" + managed + "")
return parent, bom
def test_release_poms_require_ga_versions(self):
validate_poms(self.root, "4.1.0")
for revision, qqq_version, qbit_version in (
("4.1.0-SNAPSHOT", "4.1.0", "1.2.3"),
("4.1.0", "4.1.0-SNAPSHOT", "1.2.3"),
("4.1.0", "4.1.0", "1.2.3-SNAPSHOT"),
):
with self.subTest(revision=revision, qqq=qqq_version, qbit=qbit_version):
self.write_poms(revision, qqq_version, qbit_version)
with self.assertRaises(GateError):
validate_poms(self.root, "4.1.0")
self.write_poms("4.1.0", "4.1.0", "1.2.3")
source_bom = self.root / "qqq-all-bom/pom.xml"
source_bom.write_text(source_bom.read_text().replace(
"qbit-webhooks",
"qbit-other"))
with self.assertRaises(GateError):
validate_poms(self.root, "4.1.0")
def test_smoke_run_must_match_release_sha_and_both_jobs(self):
run = {
"name": "Smoke", "path": ".github/workflows/smoke.yml",
"head_sha": RELEASE_SHA, "head_branch": "develop", "event": "push",
"conclusion": "success",
}
jobs = {"jobs": [
{"name": "core", "conclusion": "success"},
{"name": "full", "conclusion": "success"},
]}
validate_smoke(run, jobs, RELEASE_SHA)
for changed_run, changed_jobs in (
({**run, "head_sha": "b" * 40}, jobs),
({**run, "conclusion": "failure"}, jobs),
({**run, "path": ".github/workflows/other.yml"}, jobs),
({**run, "event": "workflow_dispatch"}, jobs),
({**run, "head_branch": "main"}, jobs),
(run, {"jobs": [jobs["jobs"][0]]}),
(run, {"jobs": [jobs["jobs"][0], {"name": "full", "conclusion": "skipped"}]}),
):
with self.subTest(run=changed_run, jobs=changed_jobs):
with self.assertRaises(GateError):
validate_smoke(changed_run, changed_jobs, RELEASE_SHA)
def test_published_parent_and_bom_must_be_release_poms(self):
parent, bom = self.published_poms()
validate_published_poms(parent, bom, "4.1.0", self.root / "qqq-all-bom/pom.xml")
self.published_poms(resolved=True)
validate_published_poms(parent, bom, "4.1.0", self.root / "qqq-all-bom/pom.xml")
parent.write_text(parent.read_text().replace("qqq-all-parent", "wrong-parent"))
with self.assertRaises(GateError):
validate_published_poms(parent, bom, "4.1.0", self.root / "qqq-all-bom/pom.xml")
self.published_poms(resolved=True)
bom.write_text(bom.read_text().replace("4.1.0", "4.1.0-SNAPSHOT"))
with self.assertRaises(GateError):
validate_published_poms(parent, bom, "4.1.0", self.root / "qqq-all-bom/pom.xml")
def test_published_bom_rejects_managed_dependency_overrides(self):
parent, bom = self.published_poms(resolved=True)
original = bom.read_text()
mutations = (
original.replace("qqq-bom-pom",
"qqq-other-bom"),
original.replace("pomimport",
"pomcompile"),
original.replace("4.1.0pomimport",
"4.1.0-SNAPSHOTpomimport"),
original.replace("qbit-webhooks1.2.3",
"qbit-webhooks1.2.3-SNAPSHOT"),
original.replace("qbit-webhooks1.2.3",
"qbit-webhooks1.2.4"),
original.replace("com.kingsrook.qbits"
"qbit-webhooks1.2.3"
"", ""),
original.replace("",
"com.kingsrook.qbits"
"qbit-webhooks1.2.4"
""),
original.replace("", "override"
"true"
""
"com.kingsrook.qbits"
"qbit-webhooks"
"1.2.3-SNAPSHOT"
""
""),
)
for index, mutation in enumerate(mutations):
with self.subTest(mutation=index):
self.assertNotEqual(original, mutation)
bom.write_text(mutation)
with self.assertRaises(GateError):
validate_published_poms(parent, bom, "4.1.0", self.root / "qqq-all-bom/pom.xml")
bom.write_text(original.replace(
"1.2.3",
"1.2.4"))
with self.assertRaises(GateError):
validate_published_poms(parent, bom, "4.1.0", self.root / "qqq-all-bom/pom.xml")
if __name__ == "__main__":
unittest.main()