11// Google tests cover media understanding provider.video plugin behavior.
2+ import { createServer , type Server } from "node:http" ;
23import {
34 createRequestCaptureJsonFetch ,
45 installPinnedHostnameTestHooks ,
@@ -10,6 +11,49 @@ import { resolveGoogleGenerativeAiHttpRequestConfig } from "./runtime-api.js";
1011
1112installPinnedHostnameTestHooks ( ) ;
1213
14+ const LOOPBACK_RESPONSE_BYTES = 18 * 1024 * 1024 ;
15+
16+ async function listenLoopbackServer ( server : Server ) : Promise < number > {
17+ return await new Promise ( ( resolve , reject ) => {
18+ server . once ( "error" , reject ) ;
19+ server . listen ( 0 , "127.0.0.1" , ( ) => {
20+ server . off ( "error" , reject ) ;
21+ const address = server . address ( ) ;
22+ if ( ! address || typeof address === "string" ) {
23+ reject ( new Error ( "expected loopback TCP address" ) ) ;
24+ return ;
25+ }
26+ resolve ( address . port ) ;
27+ } ) ;
28+ } ) ;
29+ }
30+
31+ function createOversizedJsonServer ( ) : { server : Server ; closed : Promise < number > } {
32+ let resolveClosed : ( sentBytes : number ) => void = ( ) => { } ;
33+ const closed = new Promise < number > ( ( resolve ) => {
34+ resolveClosed = resolve ;
35+ } ) ;
36+ const server = createServer ( ( _req , res ) => {
37+ let sentBytes = 0 ;
38+ const chunk = Buffer . alloc ( 64 * 1024 , 0x20 ) ;
39+ res . writeHead ( 200 , { "content-type" : "application/json" } ) ;
40+ const timer = setInterval ( ( ) => {
41+ if ( sentBytes >= LOOPBACK_RESPONSE_BYTES ) {
42+ clearInterval ( timer ) ;
43+ res . end ( ) ;
44+ return ;
45+ }
46+ sentBytes += chunk . length ;
47+ res . write ( chunk ) ;
48+ } , 1 ) ;
49+ res . on ( "close" , ( ) => {
50+ clearInterval ( timer ) ;
51+ resolveClosed ( sentBytes ) ;
52+ } ) ;
53+ } ) ;
54+ return { server, closed } ;
55+ }
56+
1357describe ( "describeGeminiVideo" , ( ) => {
1458 it ( "respects case-insensitive x-goog-api-key overrides" , async ( ) => {
1559 let seenKey : string | null = null ;
@@ -114,6 +158,29 @@ describe("describeGeminiVideo", () => {
114158 ) ;
115159 } ) ;
116160
161+ it ( "bounds oversized video JSON responses and closes the stream early" , async ( ) => {
162+ const { server, closed } = createOversizedJsonServer ( ) ;
163+ const port = await listenLoopbackServer ( server ) ;
164+ const fetchFn = withFetchPreconnect ( async ( ) =>
165+ fetch ( `http://127.0.0.1:${ port } /google-video-json` ) ,
166+ ) ;
167+
168+ try {
169+ await expect (
170+ describeGeminiVideo ( {
171+ buffer : Buffer . from ( "video-bytes" ) ,
172+ fileName : "clip.mp4" ,
173+ apiKey : "test-key" ,
174+ timeoutMs : 1500 ,
175+ fetchFn,
176+ } ) ,
177+ ) . rejects . toThrow ( / J S O N r e s p o n s e e x c e e d s 1 6 7 7 7 2 1 6 b y t e s / u) ;
178+ await expect ( closed ) . resolves . toBeLessThan ( LOOPBACK_RESPONSE_BYTES ) ;
179+ } finally {
180+ server . close ( ) ;
181+ }
182+ } ) ;
183+
117184 it ( "rejects non-Google video base URLs before sending authenticated requests" , async ( ) => {
118185 await expect (
119186 describeGeminiVideo ( {
0 commit comments