2929 sys.exit(1)
3030if "/contents/" in path:
3131 print("raw file", end="")
32+ elif path.endswith("/permission"):
33+ print(json.dumps({"permission": "none", "role_name": ""}))
3234elif "--slurp" in sys.argv:
3335 print(json.dumps([[{"n": 1}, {"n": 2}], [{"n": 3}]]))
3436else:
3840PATCH = "@@ -10,3 +10,4 @@ flags:\n - a\n - b\n + - c\n - d"
3941
4042
41- def pr (additions = 1 , deletions = 0 , draft = False , fork = False , sha = "abc1234def" , association = "MEMBER" ):
43+ def pr (additions = 1 , deletions = 0 , draft = False , fork = False , sha = "abc1234def" ):
4244 return {
4345 "number" : 7 , "draft" : draft , "title" : "Add an org to a flag" , "body" : "Adds one org." ,
44- "user" : {"login" : "someone" }, "author_association" : association ,
46+ "user" : {"login" : "someone" }, "author_association" : "CONTRIBUTOR" ,
4547 "additions" : additions , "deletions" : deletions ,
4648 "head" : {"sha" : sha , "repo" : {"full_name" : "external/infra" if fork else "kernel/infra" }},
4749 "base" : {"sha" : "base000" , "ref" : "main" , "repo" : {"full_name" : "kernel/infra" }},
@@ -54,12 +56,13 @@ def file(name="posthog/flags/prod.yaml", patch=PATCH, **extra):
5456
5557
5658class FakeGitHub :
57- def __init__ (self , pull , files , comments = (), contents = None , head_after = None ):
59+ def __init__ (self , pull , files , comments = (), contents = None , head_after = None , permission = "write" ):
5860 self ._pull = pull
5961 self ._files = list (files )
6062 self ._comments = list (comments )
6163 self ._contents = contents or {}
6264 self .head_after = head_after
65+ self ._permission = permission
6366 self .created = []
6467 self .updated = []
6568 self .pull_reads = 0
@@ -76,6 +79,9 @@ def files(self, number):
7679 def comments (self , number ):
7780 return self ._comments
7881
82+ def permission (self , login ):
83+ return self ._permission
84+
7985 def file_at (self , path , ref ):
8086 return self ._contents .get ((path , ref ))
8187
@@ -130,21 +136,29 @@ def test_ineligible_reasons(self):
130136 renamed = file ("b.yaml" , status = "renamed" , previous_filename = "a.yaml" , additions = 0 , changes = 0 )
131137 del renamed ["patch" ]
132138 cases = {
133- "draft" : (pr (draft = True ), [file ()]),
134- "fork" : (pr (fork = True ), [file ()]),
135- "not an org member " : (pr (association = "CONTRIBUTOR" ), [file ()]),
136- "author is not an org member " : (pr (association = "NONE" ), [file ()]),
137- "51 changed lines" : (pr (additions = 30 , deletions = 21 ), [file ()]),
138- "deny list (`identity/**`)" : (pr (), [file ("identity/access.go" )]),
139- "deny list (`.github/**`)" : (pr (), [file ("ci.yml" , previous_filename = ".github/workflows/ci.yml" )]),
140- "`bin/helper` has no reviewable text diff" : (pr (additions = 0 ), [binary ]),
141- "`b.yaml` has no reviewable text diff" : (pr (additions = 0 ), [renamed ]),
139+ "draft" : (pr (draft = True ), [file ()], "write" ),
140+ "fork" : (pr (fork = True ), [file ()], "write" ),
141+ "author does not have write access " : (pr (), [file ()], "read" ),
142+ "does not have write access " : (pr (), [file ()], "none" ),
143+ "51 changed lines" : (pr (additions = 30 , deletions = 21 ), [file ()], "write" ),
144+ "deny list (`identity/**`)" : (pr (), [file ("identity/access.go" )], "write" ),
145+ "deny list (`.github/**`)" : (pr (), [file ("ci.yml" , previous_filename = ".github/workflows/ci.yml" )], "write" ),
146+ "`bin/helper` has no reviewable text diff" : (pr (additions = 0 ), [binary ], "write" ),
147+ "`b.yaml` has no reviewable text diff" : (pr (additions = 0 ), [renamed ], "write" ),
142148 }
143- for expected , (p , files ) in cases .items ():
149+ for expected , (p , files , permission ) in cases .items ():
144150 with self .subTest (expected = expected ):
145- self .assertIn (expected , pr_risk .ineligible_reason (p , files , patterns ))
146- self .assertIsNone (pr_risk .ineligible_reason (pr (additions = 25 , deletions = 25 ), [file ()], patterns ))
147- self .assertIsNone (pr_risk .ineligible_reason (pr (association = "OWNER" ), [file ()], patterns ))
151+ self .assertIn (expected , pr_risk .ineligible_reason (p , files , patterns , permission ))
152+ # pr() reports CONTRIBUTOR, as the API does for every private member; it must not matter.
153+ self .assertIsNone (pr_risk .ineligible_reason (pr (additions = 25 , deletions = 25 ), [file ()], patterns , "write" ))
154+ self .assertIsNone (pr_risk .ineligible_reason (pr (), [file ()], patterns , "admin" ))
155+
156+ def test_bot_author_is_silent (self ):
157+ gh = FakeGitHub (pr (), [file ()], permission = "none" )
158+ gate , code , model_input = self .run_pr (gh )
159+ self .assertEqual (gate ["reason" ], "author does not have write access" )
160+ self .assertIsNone (model_input )
161+ self .assertEqual ((gh .created , gh .updated ), ([], []))
148162
149163 def test_ineligible_without_comment_is_silent (self ):
150164 gh = FakeGitHub (pr (additions = 200 ), [file ()])
@@ -262,12 +276,14 @@ def test_github_client_calls_gh(self):
262276 self .assertEqual (gh .file_at ("dir/a b.yaml" , "abc" ), "raw file" )
263277 self .assertIsNone (gh .file_at ("missing.yaml" , "abc" ))
264278 gh .update_comment (9 , "hello" )
279+ self .assertEqual (gh .permission ("kernel-internal[bot]" ), "none" )
265280 calls = json .loads ((root / "log.json" ).read_text ())
266281 self .assertEqual (calls [0 ]["args" ], ["api" , "--paginate" , "--slurp" , "/repos/kernel/infra/issues/7/comments?per_page=100" ])
267282 self .assertIn ("/repos/kernel/infra/contents/dir/a%20b.yaml?ref=abc" , calls [1 ]["args" ])
268283 self .assertIn ("Accept: application/vnd.github.raw" , calls [1 ]["args" ])
269284 self .assertEqual (calls [3 ]["args" ][:4 ], ["api" , "-X" , "PATCH" , "/repos/kernel/infra/issues/comments/9" ])
270285 self .assertEqual (json .loads (calls [3 ]["stdin" ]), {"body" : "hello" })
286+ self .assertEqual (calls [4 ]["args" ], ["api" , "/repos/kernel/infra/collaborators/kernel-internal%5Bbot%5D/permission" ])
271287
272288 def test_workflow_isolates_the_model (self ):
273289 workflow = yaml .safe_load (WORKFLOW .read_text ())
@@ -280,12 +296,7 @@ def test_workflow_isolates_the_model(self):
280296 names = list (steps )
281297 self .assertLess (names .index ("Classify" ), names .index ("Generate app token" ))
282298 self .assertEqual (steps ["Generate app token" ]["with" ]["permission-pull-requests" ], "write" )
283- # The workflow token and event payload both report private members as CONTRIBUTOR.
284- self .assertEqual (steps ["Gather" ]["env" ]["GH_TOKEN" ], "${{ steps.read-token.outputs.token }}" )
285- read = steps ["Generate read token" ]["with" ]
286- self .assertEqual ({k : v for k , v in read .items () if k .startswith ("permission-" )},
287- {"permission-contents" : "read" , "permission-pull-requests" : "read" })
288- self .assertLess (names .index ("Generate read token" ), names .index ("Gather" ))
299+ self .assertEqual (steps ["Gather" ]["env" ]["GH_TOKEN" ], "${{ github.token }}" )
289300 triggers = workflow [True ]["workflow_call" ]
290301 self .assertEqual (set (triggers ["secrets" ]), {"ADMIN_APP_ID" , "ADMIN_APP_PRIVATE_KEY" , "ANTHROPIC_API_KEY" })
291302
0 commit comments