11import { describe , expect , it , vi } from "vitest" ;
22import { fetchProofComments } from "../../scripts/github/real-behavior-proof-check.mjs" ;
33
4+ function stalledResponse ( ) {
5+ let keepAlive : ReturnType < typeof setTimeout > | undefined ;
6+ const reader = {
7+ read : ( ) =>
8+ new Promise < ReadableStreamReadResult < Uint8Array > > ( ( ) => {
9+ keepAlive = setTimeout ( ( ) => { } , 10_000 ) ;
10+ } ) ,
11+ cancel : vi . fn ( ( ) => {
12+ if ( keepAlive ) {
13+ clearTimeout ( keepAlive ) ;
14+ }
15+ return Promise . resolve ( ) ;
16+ } ) ,
17+ releaseLock : vi . fn ( ) ,
18+ } ;
19+ return {
20+ ok : true ,
21+ status : 200 ,
22+ headers : new Headers ( ) ,
23+ body : {
24+ getReader : ( ) => reader ,
25+ } ,
26+ } ;
27+ }
28+
29+ function contentLengthResponse ( contentLength : number ) {
30+ const cancel = vi . fn ( ( ) => Promise . resolve ( ) ) ;
31+ return {
32+ ok : true ,
33+ status : 200 ,
34+ headers : new Headers ( { "content-length" : String ( contentLength ) } ) ,
35+ body : { cancel } ,
36+ cancel,
37+ } ;
38+ }
39+
40+ function chunkedResponse ( chunks : Uint8Array [ ] ) {
41+ const cancel = vi . fn ( ( ) => Promise . resolve ( ) ) ;
42+ const read = vi . fn ( ) ;
43+ for ( const chunk of chunks ) {
44+ read . mockResolvedValueOnce ( { done : false , value : chunk } ) ;
45+ }
46+ read . mockResolvedValueOnce ( { done : true , value : undefined } ) ;
47+ return {
48+ ok : true ,
49+ status : 200 ,
50+ headers : new Headers ( ) ,
51+ body : {
52+ getReader : ( ) => ( {
53+ read,
54+ cancel,
55+ releaseLock : vi . fn ( ) ,
56+ } ) ,
57+ } ,
58+ } ;
59+ }
60+
461describe ( "real-behavior-proof-check GitHub lookups" , ( ) => {
562 it ( "aborts stalled proof comment fetches" , async ( ) => {
663 const fetch = vi . fn ( ( _url : URL , init : RequestInit ) => {
@@ -22,11 +79,7 @@ describe("real-behavior-proof-check GitHub lookups", () => {
2279 } ) ;
2380
2481 it ( "times out stalled proof comment response bodies" , async ( ) => {
25- const fetch = vi . fn ( ) . mockResolvedValue ( {
26- ok : true ,
27- status : 200 ,
28- json : ( ) => new Promise ( ( ) => { } ) ,
29- } ) ;
82+ const fetch = vi . fn ( ) . mockResolvedValue ( stalledResponse ( ) ) ;
3083
3184 await expect (
3285 fetchProofComments ( {
@@ -39,4 +92,88 @@ describe("real-behavior-proof-check GitHub lookups", () => {
3992 } ) ,
4093 ) . rejects . toThrow ( / p r o o f c o m m e n t r e s p o n s e p a g e 1 t i m e d o u t a f t e r 5 m s / ) ;
4194 } ) ;
95+
96+ it ( "skips oversized proof comment bodies by content length after narrow fallback" , async ( ) => {
97+ const response = contentLengthResponse ( 1024 * 1024 + 1 ) ;
98+ const fetch = vi . fn ( ( url : URL ) => {
99+ const perPage = url . searchParams . get ( "per_page" ) ;
100+ const page = url . searchParams . get ( "page" ) ;
101+ if ( ( perPage === "100" && page === "1" ) || ( perPage === "1" && page === "1" ) ) {
102+ return Promise . resolve ( response ) ;
103+ }
104+ return Promise . resolve ( new Response ( "[]" , { status : 200 } ) ) ;
105+ } ) ;
106+
107+ await expect (
108+ fetchProofComments ( {
109+ fetchImpl : fetch as typeof globalThis . fetch ,
110+ issueNumber : 123 ,
111+ owner : "openclaw" ,
112+ repo : "openclaw" ,
113+ tokens : [ "tok" ] ,
114+ } ) ,
115+ ) . resolves . toEqual ( [ ] ) ;
116+ expect ( response . cancel ) . toHaveBeenCalled ( ) ;
117+ } ) ;
118+
119+ it ( "skips oversized streamed proof comment bodies after narrow fallback" , async ( ) => {
120+ const encoder = new TextEncoder ( ) ;
121+ const oversizedResponse = ( ) =>
122+ chunkedResponse ( [
123+ encoder . encode ( "[" ) ,
124+ encoder . encode ( " " . repeat ( 1024 * 1024 ) ) ,
125+ encoder . encode ( "]" ) ,
126+ ] ) ;
127+ const fetch = vi . fn ( ( url : URL ) => {
128+ const perPage = url . searchParams . get ( "per_page" ) ;
129+ const page = url . searchParams . get ( "page" ) ;
130+ if ( ( perPage === "100" && page === "1" ) || ( perPage === "1" && page === "1" ) ) {
131+ return Promise . resolve ( oversizedResponse ( ) ) ;
132+ }
133+ return Promise . resolve ( new Response ( "[]" , { status : 200 } ) ) ;
134+ } ) ;
135+
136+ await expect (
137+ fetchProofComments ( {
138+ fetchImpl : fetch as typeof globalThis . fetch ,
139+ issueNumber : 123 ,
140+ owner : "openclaw" ,
141+ repo : "openclaw" ,
142+ tokens : [ "tok" ] ,
143+ } ) ,
144+ ) . resolves . toEqual ( [ ] ) ;
145+ } ) ;
146+
147+ it ( "falls back to one-comment pages when a bulk comment page is oversized" , async ( ) => {
148+ const fetch = vi . fn ( ( url : URL ) => {
149+ const perPage = url . searchParams . get ( "per_page" ) ;
150+ const page = url . searchParams . get ( "page" ) ;
151+ if ( perPage === "100" && page === "1" ) {
152+ return Promise . resolve ( contentLengthResponse ( 1024 * 1024 + 1 ) ) ;
153+ }
154+ if ( perPage === "1" && page === "1" ) {
155+ return Promise . resolve ( contentLengthResponse ( 1024 * 1024 + 1 ) ) ;
156+ }
157+ if ( perPage === "1" && page === "2" ) {
158+ return Promise . resolve ( new Response ( '[{"id":2,"body":"trusted proof"}]' , { status : 200 } ) ) ;
159+ }
160+ return Promise . resolve ( new Response ( "[]" , { status : 200 } ) ) ;
161+ } ) ;
162+
163+ await expect (
164+ fetchProofComments ( {
165+ fetchImpl : fetch as typeof globalThis . fetch ,
166+ issueNumber : 123 ,
167+ owner : "openclaw" ,
168+ repo : "openclaw" ,
169+ tokens : [ "tok" ] ,
170+ } ) ,
171+ ) . resolves . toEqual ( [ { id : 2 , body : "trusted proof" } ] ) ;
172+
173+ expect (
174+ fetch . mock . calls . map (
175+ ( [ url ] ) => `${ url . searchParams . get ( "per_page" ) } :${ url . searchParams . get ( "page" ) } ` ,
176+ ) ,
177+ ) . toEqual ( [ "100:1" , "1:1" , "1:2" , "1:3" ] ) ;
178+ } ) ;
42179} ) ;
0 commit comments