"""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()