"""tests/test_submission_readiness.py — REQ-220. Covers: good contract passes; missing tags fail with MISSING_TAGS; env-missing-mandatory fails with ENV_MISSING_MANDATORY::; agentic profile missing intent fails with AGENTIC_MISSING_INTENT; missing appSource fails with MISSING_APP_SOURCE. """ import json import os import sys import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from core.submission_readiness import check_readiness, ReadinessResult GOOD_TAGS = { "nova:owner": "consumer-repo", "nova:contract": "uuid-1234", "nova:environment": "dev", "nova:cost-center": "nova-default", "nova:ref": "CHG0678912", } def _base(**overrides): submission = { "contractId": "uuid-1234", "id": "webapi", "name": "Customer Web API", "environment": "dev", "tags": dict(GOOD_TAGS), "policyPreconditions": {"public-ingress": False, "encryption_enabled": True}, "profile": "developer", "appSource": {"repo": "consumer/repo", "ref": "main"}, "infrastructure": { "static-assets": {"inputs": {"bucket_name": "webapi-assets"}} }, } submission.update(overrides) return submission class TestGoodContract(unittest.TestCase): def test_good_contract_passes(self): result = check_readiness(_base()) self.assertTrue(result.ready, f"Expected ready, got: {result.reason_codes}") self.assertEqual(result.contract_id, "uuid-1234") def test_good_agentic_contract_passes(self): submission = _base( profile="agentic", naturalLanguageIntent="A web API for customer data", confidenceAtSubmission=0.85, agentTrace="LLM generated contract from issue #42", ) result = check_readiness(submission) self.assertTrue(result.ready, f"Expected ready, got: {result.reason_codes}") class TestMissingTags(unittest.TestCase): def test_missing_tags_fail(self): submission = _base() submission["tags"] = {"nova:owner": "consumer-repo"} result = check_readiness(submission) self.assertFalse(result.ready) codes = " ".join(result.reason_codes) self.assertIn("MISSING_TAGS", codes) self.assertIn("nova:contract", codes) self.assertIn("nova:environment", codes) self.assertIn("nova:cost-center", codes) self.assertIn("nova:ref", codes) def test_empty_tag_value_fails(self): submission = _base() submission["tags"]["nova:owner"] = "" result = check_readiness(submission) self.assertFalse(result.ready) self.assertTrue(any("MISSING_TAGS" in c for c in result.reason_codes)) class TestEnvMissingMandatory(unittest.TestCase): def test_qa_missing_e2e_suite_fails(self): submission = _base(environment="qa") submission["tags"]["nova:environment"] = "qa" # No validation.e2eSuite result = check_readiness(submission) self.assertFalse(result.ready) codes = " ".join(result.reason_codes) self.assertIn("ENV_MISSING_MANDATORY:qa:validation.e2eSuite", codes) def test_prod_missing_runbook_fails(self): submission = _base(environment="prod") submission["tags"]["nova:environment"] = "prod" # No runbook/dashboard/oncall result = check_readiness(submission) self.assertFalse(result.ready) codes = " ".join(result.reason_codes) self.assertIn("ENV_MISSING_MANDATORY:prod:runbook", codes) self.assertIn("ENV_MISSING_MANDATORY:prod:dashboard", codes) self.assertIn("ENV_MISSING_MANDATORY:prod:oncall", codes) def test_dr_missing_drdrillref_fails(self): submission = _base(environment="dr") submission["tags"]["nova:environment"] = "dr" result = check_readiness(submission) self.assertFalse(result.ready) codes = " ".join(result.reason_codes) self.assertIn("ENV_MISSING_MANDATORY:dr:drDrillRef", codes) def test_prod_with_all_mandatory_passes(self): submission = _base( environment="prod", runbook="docs/runbooks/webapi.md", dashboard="https://grafana/nova/webapi", oncall="oncall@company.com", ) submission["tags"]["nova:environment"] = "prod" result = check_readiness(submission) self.assertTrue(result.ready, f"Expected ready, got: {result.reason_codes}") class TestAgenticMissingIntent(unittest.TestCase): def test_agentic_missing_all_markers_fails(self): submission = _base(profile="agentic") result = check_readiness(submission) self.assertFalse(result.ready) codes = " ".join(result.reason_codes) self.assertIn("AGENTIC_MISSING_INTENT:naturalLanguageIntent", codes) self.assertIn("AGENTIC_MISSING_INTENT:confidenceAtSubmission", codes) self.assertIn("AGENTIC_MISSING_INTENT:agentTrace", codes) def test_agentic_missing_one_marker_fails(self): submission = _base( profile="agentic", naturalLanguageIntent="A web API", confidenceAtSubmission=0.85, # agentTrace missing ) result = check_readiness(submission) self.assertFalse(result.ready) self.assertTrue(any("agentTrace" in c for c in result.reason_codes)) class TestMissingAppSource(unittest.TestCase): def test_missing_appsource_fails(self): submission = _base() del submission["appSource"] result = check_readiness(submission) self.assertFalse(result.ready) self.assertTrue(any("MISSING_APP_SOURCE" in c for c in result.reason_codes)) def test_appsource_missing_ref_fails(self): submission = _base() submission["appSource"] = {"repo": "consumer/repo"} result = check_readiness(submission) self.assertFalse(result.ready) self.assertTrue(any("MISSING_APP_SOURCE" in c for c in result.reason_codes)) class TestPolicyPreconditionMissing(unittest.TestCase): def test_empty_policy_fails(self): submission = _base() submission["policyPreconditions"] = {} result = check_readiness(submission) self.assertFalse(result.ready) self.assertTrue(any("POLICY_PRECONDITION_MISSING" in c for c in result.reason_codes)) class TestReadinessResultStructure(unittest.TestCase): def test_result_to_dict(self): result = check_readiness(_base()) d = result.to_dict() self.assertIn("ready", d) self.assertIn("reason_codes", d) self.assertIn("contractId", d) def test_result_str_ready(self): result = check_readiness(_base()) self.assertIn("READY", str(result)) def test_result_str_not_ready(self): submission = _base() del submission["appSource"] result = check_readiness(submission) self.assertIn("NOT READY", str(result)) if __name__ == "__main__": unittest.main()