Files
acdl/tests/test_submission_readiness.py
T
Jon Chery 5775a97388 feat(P3): submission-readiness input contract — schema + validator + docs + tests (REQ-217..220)
REQ-217: schemas/submission-readiness.schema.json (JSON Schema draft 2020-12)
defines acceptable-to-start as a superset gate above contract.schema.json:
contractId, environment, tags (5 Nova tags D-054), policyPreconditions,
profile (developer|agentic), appSource (repo+ref), per-env mandatory (W3.E:
qa→e2eSuite+loadTest, prod→runbook+dashboard+oncall, dr→drDrillRef),
agentic markers (naturalLanguageIntent+confidenceAtSubmission+agentTrace).

REQ-218: core/submission_readiness.py validator with check_readiness() +
ReadinessResult (structured pass/fail + reason codes). Wired as
contract_ingestor.py --check-readiness (D-133). Reason codes: MISSING_TAGS,
ENV_MISSING_MANDATORY, AGENTIC_MISSING_INTENT, MISSING_APP_SOURCE,
POLICY_PRECONDITION_MISSING. Never raises — all failures are reason codes.

REQ-219: docs/submission-readiness.md (good + rejected examples +
reason-code catalog + compliance-standard equivalence).

REQ-220: tests/test_submission_readiness.py — 16 tests, all pass.
Covers: good-pass, good-agentic-pass, missing-tags, empty-tag,
qa-missing-e2e, prod-missing-runbook, dr-missing-drdrill, prod-all-pass,
agentic-missing-all, agentic-missing-one, missing-appsource,
appsource-missing-ref, empty-policy, result-structure.

---ci---
project: acdl
phase: 3
milestone: v1.18
status: execute
requirements:
  covered: [REQ-217, REQ-218, REQ-219, REQ-220]
  partial: []
---/ci---
2026-08-06 15:09:29 +00:00

189 lines
6.9 KiB
Python

"""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:<env>:<field>;
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()