@@ -218,6 +218,13 @@ func TestClassifyPRReviewSignal(t *testing.T) {
218218 editorLogin : "chatgpt-codex-connector" ,
219219 want : domain .PRReviewSignalApproved ,
220220 },
221+ {
222+ name : "codex editor eyes dominate earlier thumbs up" ,
223+ body : "Codex review complete 👍\n Codex review 👀" ,
224+ editorType : "Bot" ,
225+ editorLogin : "chatgpt-codex-connector" ,
226+ want : domain .PRReviewSignalReviewing ,
227+ },
221228 {
222229 name : "non codex editor standalone thumbs up" ,
223230 body : "👍" ,
@@ -250,36 +257,106 @@ func TestClassifyPRReviewSignal(t *testing.T) {
250257 }
251258}
252259
253- func TestCanFastSettleReview (t * testing.T ) {
254- if ! CanFastSettleReview (& domain.ActivitySnapshot {
255- HeadSHA : "abc123" ,
256- PRReviewSignal : domain .PRReviewSignalApproved ,
257- }) {
258- t .Fatal ("approved PR signal with no unresolved threads should fast-settle" )
260+ func TestClassifyPRReactionSignal (t * testing.T ) {
261+ if got := classifyPRReactionSignal (codexReactionConnection (), reactionConnection {}); got != domain .PRReviewSignalReviewing {
262+ t .Fatalf ("eyes reaction signal = %q, want %q" , got , domain .PRReviewSignalReviewing )
259263 }
260264
261- if CanFastSettleReview (& domain.ActivitySnapshot {
262- HeadSHA : "abc123" ,
263- PRReviewSignal : domain .PRReviewSignalApproved ,
264- ThreadCount : 101 ,
265- ThreadIDs : make ([]string , 100 ),
266- }) {
267- t .Fatal ("incomplete thread probe should block fast-settle" )
265+ if got := classifyPRReactionSignal (reactionConnection {}, codexReactionConnection ()); got != domain .PRReviewSignalApproved {
266+ t .Fatalf ("thumbs-up reaction signal = %q, want %q" , got , domain .PRReviewSignalApproved )
268267 }
269268
270- if CanFastSettleReview (& domain.ActivitySnapshot {
271- HeadSHA : "abc123" ,
272- PRReviewSignal : domain .PRReviewSignalApproved ,
273- UnresolvedThreadCount : 1 ,
274- }) {
275- t .Fatal ("unresolved threads should block fast-settle" )
269+ if got := classifyPRReactionSignal (codexReactionConnection (), codexReactionConnection ()); got != domain .PRReviewSignalReviewing {
270+ t .Fatalf ("eyes should dominate thumbs-up reaction, got %q" , got )
276271 }
277272
278- if CanFastSettleReview (& domain.ActivitySnapshot {
279- HeadSHA : "abc123" ,
280- PRReviewSignal : domain .PRReviewSignalApproved ,
281- ReviewDecision : string (domain .ReviewChangesRequested ),
282- }) {
283- t .Fatal ("changes-requested review decision should block fast-settle" )
273+ if got := classifyPRReactionSignal (humanReactionConnection (), codexReactionConnection ()); got != domain .PRReviewSignalApproved {
274+ t .Fatalf ("non-codex eyes reaction should not block codex thumbs-up, got %q" , got )
275+ }
276+
277+ if got := combinePRReviewSignals (domain .PRReviewSignalApproved , domain .PRReviewSignalReviewing ); got != domain .PRReviewSignalReviewing {
278+ t .Fatalf ("eyes should dominate combined signals, got %q" , got )
279+ }
280+ }
281+
282+ func TestCanFastSettleReview (t * testing.T ) {
283+ tests := []struct {
284+ name string
285+ snap * domain.ActivitySnapshot
286+ want bool
287+ }{
288+ {
289+ name : "approved signal with no unresolved threads" ,
290+ snap : & domain.ActivitySnapshot {
291+ HeadSHA : "abc123" ,
292+ PRReviewSignal : domain .PRReviewSignalApproved ,
293+ },
294+ want : true ,
295+ },
296+ {
297+ name : "incomplete thread probe" ,
298+ snap : & domain.ActivitySnapshot {
299+ HeadSHA : "abc123" ,
300+ PRReviewSignal : domain .PRReviewSignalApproved ,
301+ ThreadCount : 101 ,
302+ ThreadIDs : make ([]string , 100 ),
303+ },
304+ want : false ,
305+ },
306+ {
307+ name : "unresolved threads" ,
308+ snap : & domain.ActivitySnapshot {
309+ HeadSHA : "abc123" ,
310+ PRReviewSignal : domain .PRReviewSignalApproved ,
311+ UnresolvedThreadCount : 1 ,
312+ },
313+ want : false ,
314+ },
315+ {
316+ name : "changes requested decision" ,
317+ snap : & domain.ActivitySnapshot {
318+ HeadSHA : "abc123" ,
319+ PRReviewSignal : domain .PRReviewSignalApproved ,
320+ ReviewDecision : string (domain .ReviewChangesRequested ),
321+ },
322+ want : false ,
323+ },
324+ }
325+
326+ for _ , tt := range tests {
327+ t .Run (tt .name , func (t * testing.T ) {
328+ if got := CanFastSettleReview (tt .snap ); got != tt .want {
329+ t .Fatalf ("CanFastSettleReview() = %v, want %v" , got , tt .want )
330+ }
331+ })
332+ }
333+ }
334+
335+ func codexReactionConnection () reactionConnection {
336+ return reactionConnectionWithUser ("Bot" , "chatgpt-codex-connector" )
337+ }
338+
339+ func humanReactionConnection () reactionConnection {
340+ return reactionConnectionWithUser ("User" , "alice" )
341+ }
342+
343+ func reactionConnectionWithUser (typeName , login string ) reactionConnection {
344+ return reactionConnection {
345+ Nodes : []struct {
346+ User * struct {
347+ TypeName string `json:"__typename"`
348+ Login string `json:"login"`
349+ } `json:"user"`
350+ }{
351+ {
352+ User : & struct {
353+ TypeName string `json:"__typename"`
354+ Login string `json:"login"`
355+ }{
356+ TypeName : typeName ,
357+ Login : login ,
358+ },
359+ },
360+ },
284361 }
285362}
0 commit comments