@@ -482,7 +482,8 @@ def timeout(*args, **kwargs):
482482
483483def report (name ):
484484 return {"schema_version" : 1 , "check_name" : name , "status" : "passed" ,
485- "base_sha" : "b" * 40 , "head_sha" : "c" * 40 , "policy_sha" : "d" * 64 }
485+ "base_sha" : "b" * 40 , "head_sha" : "c" * 40 , "policy_sha" : "d" * 64 ,
486+ "findings" : []}
486487
487488
488489def test_aggregate_requires_fixed_actual_results_and_fresh_reports ():
@@ -499,6 +500,114 @@ def test_aggregate_requires_fixed_actual_results_and_fresh_reports():
499500 assert quality .aggregate ({}, reports , "b" * 40 , "c" * 40 )
500501
501502
503+ @pytest .fixture
504+ def checker ():
505+ spec = importlib .util .spec_from_file_location ("check_quality_under_test" , SCRIPT .with_name ("check-quality.py" ))
506+ assert spec and spec .loader
507+ module = importlib .util .module_from_spec (spec )
508+ spec .loader .exec_module (module )
509+ return module
510+
511+
512+ @pytest .mark .parametrize (("field" , "value" ), [
513+ ("line" , True ), ("line" , "1" ), ("line" , 0 ), ("column" , None ),
514+ ("column" , False ), ("file" , "" ), ("message" , []), ("code" , None ),
515+ ("severity" , "warning" ),
516+ ])
517+ def test_malformed_mypy_diagnostics_are_execution_errors (checker , monkeypatch , tmp_path , field , value ):
518+ diagnostic = {
519+ "file" : str (tmp_path / "a.py" ), "line" : 1 , "column" : 0 ,
520+ "message" : "bad return type" , "code" : "return-value" , "severity" : "error" ,
521+ }
522+ diagnostic [field ] = value
523+ monkeypatch .setattr (quality , "run_tool" , lambda * a , ** k : subprocess .CompletedProcess (
524+ [], 1 , json .dumps (diagnostic ), "" ,
525+ ))
526+ with pytest .raises (quality .QualityError ):
527+ checker .typing (tmp_path , tmp_path / "pyproject.toml" , {"a.py" : "value = 1" },
528+ {"a" : "a.py" }, {"a" }, {"entries" : []})
529+
530+
531+ @pytest .mark .parametrize (("returncode" , "diagnostics" ), [(1 , "" ), (0 , "error" )])
532+ def test_mypy_status_must_match_diagnostics (checker , monkeypatch , tmp_path , returncode , diagnostics ):
533+ stdout = "" if not diagnostics else json .dumps ({
534+ "file" : str (tmp_path / "a.py" ), "line" : 1 , "column" : 0 ,
535+ "message" : "bad return type" , "code" : "return-value" , "severity" : "error" ,
536+ })
537+ monkeypatch .setattr (quality , "run_tool" , lambda * a , ** k : subprocess .CompletedProcess (
538+ [], returncode , stdout , "" ,
539+ ))
540+ with pytest .raises (quality .QualityError ):
541+ checker .typing (tmp_path , tmp_path / "pyproject.toml" , {"a.py" : "value = 1" },
542+ {"a" : "a.py" }, {"a" }, {"entries" : []})
543+
544+
545+ @pytest .mark .parametrize (("field" , "value" ), [
546+ ("code" , None ), ("filename" , "" ), ("message" , []),
547+ ("location" , {"row" : True , "column" : 1 }), ("location" , {"row" : 0 , "column" : 1 }),
548+ ])
549+ def test_malformed_ruff_diagnostics_are_execution_errors (checker , monkeypatch , tmp_path , field , value ):
550+ diagnostic = {
551+ "filename" : str (tmp_path / "a.py" ), "location" : {"row" : 1 , "column" : 1 },
552+ "message" : "undefined name" , "code" : "F821" ,
553+ }
554+ diagnostic [field ] = value
555+ monkeypatch .setattr (quality , "run_tool" , lambda * a , ** k : subprocess .CompletedProcess (
556+ [], 1 , json .dumps ([diagnostic ]), "" ,
557+ ))
558+ with pytest .raises (quality .QualityError ):
559+ checker .lint (tmp_path , tmp_path / "pyproject.toml" , {"a.py" : "missing" }, [], {"a" : "a.py" }, "initial" )
560+
561+
562+ @pytest .mark .parametrize ("mutation" , [
563+ "none" , "missing-job" , "skipped-job" , "error-job" , "missing-report" ,
564+ "wrong-head" , "wrong-base" , "wrong-policy" , "wrong-source" , "wrong-run" , "wrong-attempt" ,
565+ "status-contradiction" , "skipped-test" , "stale-test" , "wrong-test-attempt" , "duplicate-test-key" ,
566+ ])
567+ def test_aggregate_cli_requires_actual_consistent_current_receipts (tmp_path , mutation ):
568+ jobs = {name : {"result" : "success" } for name in quality .REQUIRED_CHECKS }
569+ for name in quality .REQUIRED_CHECKS [:- 1 ]:
570+ data = dict (report (name ), repository = "Azure/gpt-rag-ingestion" , source_sha = "a" * 64 ,
571+ run_id = "123" , run_attempt = "1" )
572+ if name == "lint" :
573+ changes = {
574+ "wrong-head" : ("head_sha" , "e" * 40 ), "wrong-base" : ("base_sha" , "e" * 40 ),
575+ "wrong-policy" : ("policy_sha" , "e" * 64 ), "wrong-source" : ("source_sha" , "e" * 64 ),
576+ "wrong-run" : ("run_id" , "124" ), "wrong-attempt" : ("run_attempt" , "2" ),
577+ "status-contradiction" : ("findings" , [{"rule" : "execution-error" , "reason" : "crash" }]),
578+ }
579+ if mutation in changes :
580+ key , value = changes [mutation ]
581+ data [key ] = value
582+ if mutation == "missing-report" :
583+ continue
584+ data ["artifact_integrity" ] = quality .digest (data )
585+ (tmp_path / f"{ name } .json" ).write_text (json .dumps (data ), encoding = "utf-8" )
586+ if mutation == "missing-job" :
587+ del jobs ["unit-tests" ]
588+ elif mutation in {"skipped-job" , "error-job" }:
589+ jobs ["unit-tests" ]["result" ] = mutation .removesuffix ("-job" )
590+ evidence = {
591+ "schema_version" : 1 , "base_sha" : "b" * 40 ,
592+ "head_sha" : "e" * 40 if mutation == "stale-test" else "c" * 40 ,
593+ "run_id" : "123" , "run_attempt" : "2" if mutation == "wrong-test-attempt" else "1" ,
594+ "junit_sha" : "f" * 64 ,
595+ "tests" : {"tests/test_failure.py::test_error" : "skipped" if mutation == "skipped-test" else "passed" },
596+ }
597+ evidence ["artifact_integrity" ] = quality .digest (evidence )
598+ encoded = json .dumps (evidence )
599+ if mutation == "duplicate-test-key" :
600+ encoded = encoded .replace ('"tests": {' , '"tests": {}, "tests": {' )
601+ (tmp_path / "test-evidence.json" ).write_text (encoded , encoding = "utf-8" )
602+ result = subprocess .run (
603+ [sys .executable , "-I" , str (SCRIPT .with_name ("quality-gate.py" )),
604+ "--reports" , str (tmp_path ), "--base-sha" , "b" * 40 , "--head-sha" , "c" * 40 ],
605+ env = dict (os .environ , NEEDS_JSON = json .dumps (jobs ), GITHUB_RUN_ID = "123" , GITHUB_RUN_ATTEMPT = "1" ),
606+ capture_output = True , text = True , timeout = 30 ,
607+ )
608+ assert (result .returncode == 0 ) == (mutation == "none" ), result .stderr
609+
610+
502611@pytest .fixture
503612def policy_repository (tmp_path ):
504613 """A committed minimum policy and a separate trusted evaluator copy."""
0 commit comments