@@ -1004,6 +1004,33 @@ def test_pr_title_check_runs_when_enabled(self):
10041004 "PR title" , ["--message" ], input_text = "feat: a feature"
10051005 )
10061006
1007+ def test_pr_title_is_not_held_to_commit_only_rules (self ):
1008+ # require_body and require_signed_off_by judge a commit; a title has
1009+ # no body or trailers, so it could never pass them.
1010+ title = main .ScopeResult (
1011+ label = "PR title" ,
1012+ checks = [
1013+ make_check ("message" , "pass" , "CC001" , value = "feat: add login" ),
1014+ make_check ("require_body" , "fail" , "CC011" , value = "feat: add login" ),
1015+ make_check ("require_signed_off_by" , "fail" , "CC012" ),
1016+ make_check ("ai_author" , "fail" , "CC017" , value = "Claude <x@y.z>" ),
1017+ ],
1018+ )
1019+ with (
1020+ patch ("main.PR_TITLE_ENABLED" , True ),
1021+ patch ("main.MESSAGE_ENABLED" , False ),
1022+ patch ("main.BRANCH_ENABLED" , False ),
1023+ patch ("main.AUTHOR_NAME_ENABLED" , False ),
1024+ patch ("main.AUTHOR_EMAIL_ENABLED" , False ),
1025+ patch ("main.is_pr_event" , return_value = True ),
1026+ patch ("main.get_pr_title" , return_value = "feat: add login" ),
1027+ patch ("main.check_scope" , return_value = title ),
1028+ patch ("main.run_other_checks" , return_value = []),
1029+ ):
1030+ rc , results = main .run_commit_check ()
1031+ self .assertEqual (rc , 0 )
1032+ self .assertEqual ([c ["rule_id" ] for c in results [0 ].checks ], ["CC001" ])
1033+
10071034 def test_pr_title_failure_propagates (self ):
10081035 with (
10091036 patch ("main.PR_TITLE_ENABLED" , True ),
@@ -2744,6 +2771,95 @@ def test_failure_matches_job_summary(self):
27442771 self .assertIn ("```diff\n @@ Commit 1/1 @@\n " , comment )
27452772
27462773
2774+ class TestPrCommentSizeLimit (unittest .TestCase ):
2775+ """A report too long for a comment gives up its longest parts first."""
2776+
2777+ RUN_URL = "https://github.com/o/r/actions/runs/42"
2778+
2779+ def setUp (self ):
2780+ self .RUN = {
2781+ "GITHUB_REPOSITORY" : "o/r" ,
2782+ "GITHUB_RUN_ID" : "42" ,
2783+ "GITHUB_SERVER_URL" : "https://github.com" ,
2784+ }
2785+
2786+ def _failing_commits (self , count : int ) -> list [main .ScopeResult ]:
2787+ body = "\n " .join (
2788+ f"Line { i } of a body that explains the change." for i in range (8 )
2789+ )
2790+ return [
2791+ main .ScopeResult (
2792+ label = f"Commit { i } /{ count } " ,
2793+ sha = f"{ i :040x} " ,
2794+ checks = [
2795+ make_check (
2796+ "require_signed_off_by" ,
2797+ "fail" ,
2798+ "CC012" ,
2799+ value = f"Update module { i } \n \n { body } " ,
2800+ error = "Signed-off-by trailer not found in the commit message" ,
2801+ fix = f"Update module { i } \n \n { body } \n \n Signed-off-by: A <a@b.c>" ,
2802+ )
2803+ ],
2804+ )
2805+ for i in range (1 , count + 1 )
2806+ ]
2807+
2808+ def test_a_report_that_fits_is_the_job_summary (self ):
2809+ results = self ._failing_commits (3 )
2810+ self .assertEqual (
2811+ main .render_pr_comment (results ), main .render_job_summary (results )
2812+ )
2813+
2814+ def test_dozens_of_failing_commits_still_fit (self ):
2815+ results = self ._failing_commits (60 )
2816+ self .assertGreater (len (main .render_job_summary (results )), main .COMMENT_LIMIT )
2817+ with (
2818+ patch .dict (os .environ , self .RUN ),
2819+ patch ("main.JOB_SUMMARY_ENABLED" , True ),
2820+ patch ("main.GITHUB_STEP_SUMMARY" , "/tmp/summary.md" ),
2821+ ):
2822+ comment = main .render_pr_comment (results )
2823+ self .assertLessEqual (main ._comment_length (comment ), main .COMMENT_LIMIT )
2824+ self .assertTrue (comment .startswith (main .COMMENT_MARKER ))
2825+ self .assertIn ("Commit Check failed" , comment )
2826+ self .assertNotIn ("<summary>All 60 checks</summary>" , comment )
2827+ self .assertIn (
2828+ f"All 60 checks are in the [job summary]({ self .RUN_URL } ) of this run." ,
2829+ comment ,
2830+ )
2831+
2832+ def test_the_diff_goes_too_when_the_listing_was_not_enough (self ):
2833+ results = self ._failing_commits (2 )
2834+ with (
2835+ patch .dict (os .environ , self .RUN ),
2836+ patch ("main.JOB_SUMMARY_ENABLED" , False ),
2837+ ):
2838+ short = main .render_report (results , "short" )
2839+ with patch ("main.COMMENT_LIMIT" , len (short ) - 1 ):
2840+ comment = main .render_pr_comment (results )
2841+ self .assertNotIn ("```diff" , comment )
2842+ self .assertNotIn ("How to fix</b>" , comment )
2843+ self .assertIn (
2844+ "The findings and how to fix them are in the "
2845+ f"[step log]({ self .RUN_URL } ) of this run." ,
2846+ comment ,
2847+ )
2848+ self .assertIn ("Commits: 2 of 2 failed" , comment )
2849+
2850+ def test_a_passing_headline_names_no_findings (self ):
2851+ env = {k : v for k , v in os .environ .items () if k != "GITHUB_RUN_ID" }
2852+ with (
2853+ patch .dict (os .environ , env , clear = True ),
2854+ patch ("main.JOB_SUMMARY_ENABLED" , False ),
2855+ ):
2856+ body = main .render_report ([pass_scope ("Branch" )], "headline" )
2857+ self .assertIn ("All checks are in the step log of this run." , body )
2858+
2859+ def test_length_is_counted_as_github_counts_it (self ):
2860+ self .assertEqual (main ._comment_length ("a🤖" ), 3 )
2861+
2862+
27472863class TestUnparsableScopeInTheReport (unittest .TestCase ):
27482864 """A CLI response that is not JSON is a failure with no rule to link."""
27492865
@@ -4261,6 +4377,18 @@ def test_failing_message(self):
42614377 main .render_report ([scope ]),
42624378 )
42634379
4380+ def test_a_title_meets_only_the_rules_a_title_can (self ):
4381+ with open (self .config , "w" , encoding = "utf-8" ) as f :
4382+ f .write ("[commit]\n require_body = true\n require_signed_off_by = true\n " )
4383+ rc , data = self ._run ("feat: add login page" )
4384+ failed = {c ["rule_id" ] for c in data ["checks" ] if c ["status" ] == "fail" }
4385+ self .assertEqual (rc , 1 )
4386+ self .assertEqual (failed , {"CC011" , "CC012" })
4387+ scope = main .drop_commit_only_rules (
4388+ main .ScopeResult (label = "PR title" , checks = data ["checks" ])
4389+ )
4390+ self .assertEqual (scope .status , "pass" )
4391+
42644392
42654393class TestStderrIsNotPartOfTheJson (unittest .TestCase ):
42664394 """stdout is the JSON; stderr is what the CLI has to say to a person.
0 commit comments