88import { beforeEach , describe , expect , it , vi } from 'vitest'
99import { AuthType } from '@/lib/auth/hybrid'
1010import { clearLargeValueCacheForTests } from '@/lib/execution/payloads/cache'
11+ import { createLargeArrayManifest } from '@/lib/execution/payloads/large-array-manifest'
1112import { compactExecutionPayload } from '@/lib/execution/payloads/serializer'
13+ import { storeLargeValue } from '@/lib/execution/payloads/store'
1214import { EXECUTION_RESOURCE_LIMIT_CODE } from '@/lib/execution/resource-errors'
1315import type { ExecutionResult } from '@/lib/workflows/types'
1416import { createHttpResponseFromBlock , workflowHasResponseBlock } from '@/lib/workflows/utils'
1517
16- const { mockUploadFile } = vi . hoisted ( ( ) => ( {
18+ const { mockDownloadFile, mockUploadFile, uploadedFiles } = vi . hoisted ( ( ) => ( {
19+ mockDownloadFile : vi . fn ( ) ,
1720 mockUploadFile : vi . fn ( ) ,
21+ uploadedFiles : new Map < string , Buffer > ( ) ,
1822} ) )
1923
2024const MATERIALIZATION_CONTEXT = {
@@ -26,6 +30,7 @@ const MATERIALIZATION_CONTEXT = {
2630
2731vi . mock ( '@/lib/uploads' , ( ) => ( {
2832 StorageService : {
33+ downloadFile : mockDownloadFile ,
2934 uploadFile : mockUploadFile ,
3035 } ,
3136} ) )
@@ -60,7 +65,14 @@ describe('Response block gating by auth type', () => {
6065 beforeEach ( ( ) => {
6166 vi . clearAllMocks ( )
6267 clearLargeValueCacheForTests ( )
63- mockUploadFile . mockImplementation ( async ( { customKey } ) => ( { key : customKey } ) )
68+ uploadedFiles . clear ( )
69+ mockUploadFile . mockImplementation ( async ( { customKey, file } ) => {
70+ uploadedFiles . set ( customKey , file )
71+ return { key : customKey }
72+ } )
73+ mockDownloadFile . mockImplementation (
74+ async ( { key } ) => uploadedFiles . get ( key ) ?? Buffer . from ( '{}' )
75+ )
6476 resultWithResponseBlock = buildExecutionResult ( )
6577 } )
6678
@@ -165,6 +177,211 @@ describe('Response block gating by auth type', () => {
165177 expect ( body . success ) . toBeUndefined ( )
166178 } )
167179
180+ it ( 'should materialize Response block manifests from an allowed source execution' , async ( ) => {
181+ const rows = [ { key : 'SIM-1' } , { key : 'SIM-2' } ]
182+ const manifest = await createLargeArrayManifest ( rows , {
183+ ...MATERIALIZATION_CONTEXT ,
184+ executionId : 'source-execution-1' ,
185+ } )
186+
187+ const response = await createHttpResponseFromBlock (
188+ buildExecutionResult ( {
189+ output : {
190+ data : { rows : manifest } ,
191+ status : 200 ,
192+ headers : { } ,
193+ } ,
194+ } ) ,
195+ {
196+ ...MATERIALIZATION_CONTEXT ,
197+ largeValueExecutionIds : [ 'source-execution-1' ] ,
198+ }
199+ )
200+ const body = await response . json ( )
201+
202+ expect ( body . rows ) . toEqual ( rows )
203+ } )
204+
205+ it ( 'should reject Response block manifests from non-source same-workflow executions' , async ( ) => {
206+ const manifest = await createLargeArrayManifest ( [ { key : 'SIM-stale' } ] , {
207+ ...MATERIALIZATION_CONTEXT ,
208+ executionId : 'stale-execution-1' ,
209+ } )
210+
211+ await expect (
212+ createHttpResponseFromBlock (
213+ buildExecutionResult ( {
214+ output : {
215+ data : { rows : manifest } ,
216+ status : 200 ,
217+ headers : { } ,
218+ } ,
219+ } ) ,
220+ {
221+ ...MATERIALIZATION_CONTEXT ,
222+ largeValueExecutionIds : [ 'source-execution-1' ] ,
223+ }
224+ )
225+ ) . rejects . toThrow ( 'Large execution value is not available in this execution' )
226+ } )
227+
228+ it ( 'should materialize Response block manifests inherited by the source snapshot' , async ( ) => {
229+ const rows = [ { key : 'SIM-inherited' } ]
230+ const manifest = await createLargeArrayManifest ( rows , {
231+ ...MATERIALIZATION_CONTEXT ,
232+ executionId : 'original-execution-1' ,
233+ } )
234+
235+ const response = await createHttpResponseFromBlock (
236+ buildExecutionResult ( {
237+ output : {
238+ data : { rows : manifest } ,
239+ status : 200 ,
240+ headers : { } ,
241+ } ,
242+ } ) ,
243+ {
244+ ...MATERIALIZATION_CONTEXT ,
245+ largeValueExecutionIds : [ 'source-execution-1' , 'original-execution-1' ] ,
246+ }
247+ )
248+
249+ const body = await response . json ( )
250+
251+ expect ( body . rows ) . toEqual ( rows )
252+ } )
253+
254+ it ( 'should recursively materialize refs inside Response block manifest rows' , async ( ) => {
255+ const text = 'nested' . repeat ( 2 * 1024 * 1024 )
256+ const nestedOutput = await compactExecutionPayload (
257+ { text } ,
258+ {
259+ ...MATERIALIZATION_CONTEXT ,
260+ executionId : 'original-execution-1' ,
261+ requireDurable : true ,
262+ preserveRoot : true ,
263+ }
264+ )
265+ const nestedRef = ( nestedOutput as unknown as { text : unknown } ) . text
266+ const manifest = await createLargeArrayManifest ( [ { nested : nestedRef } ] , {
267+ ...MATERIALIZATION_CONTEXT ,
268+ executionId : 'source-execution-1' ,
269+ } )
270+ const response = await createHttpResponseFromBlock (
271+ buildExecutionResult ( {
272+ output : {
273+ data : { rows : manifest } ,
274+ status : 200 ,
275+ headers : { } ,
276+ } ,
277+ } ) ,
278+ {
279+ ...MATERIALIZATION_CONTEXT ,
280+ largeValueExecutionIds : [ 'source-execution-1' ] ,
281+ }
282+ )
283+
284+ const body = await response . json ( )
285+
286+ expect ( body . rows ) . toEqual ( [ { nested : text } ] )
287+ } )
288+
289+ it ( 'should recursively materialize refs inside stored Response block objects' , async ( ) => {
290+ const text = 'nested' . repeat ( 2 * 1024 * 1024 )
291+ const nestedOutput = await compactExecutionPayload (
292+ { text } ,
293+ {
294+ ...MATERIALIZATION_CONTEXT ,
295+ executionId : 'original-execution-1' ,
296+ requireDurable : true ,
297+ preserveRoot : true ,
298+ }
299+ )
300+ const nestedRef = ( nestedOutput as unknown as { text : unknown } ) . text
301+ const storedValue = {
302+ wrapper : {
303+ nested : nestedRef ,
304+ padding : 'x' . repeat ( 2048 ) ,
305+ } ,
306+ }
307+ const storedJson = JSON . stringify ( storedValue )
308+ const storedOutput = await storeLargeValue (
309+ storedValue ,
310+ storedJson ,
311+ Buffer . byteLength ( storedJson ) ,
312+ {
313+ ...MATERIALIZATION_CONTEXT ,
314+ executionId : 'source-execution-1' ,
315+ requireDurable : true ,
316+ }
317+ )
318+
319+ const response = await createHttpResponseFromBlock (
320+ buildExecutionResult ( {
321+ output : {
322+ data : storedOutput ,
323+ status : 200 ,
324+ headers : { } ,
325+ } ,
326+ } ) ,
327+ {
328+ ...MATERIALIZATION_CONTEXT ,
329+ largeValueExecutionIds : [ 'source-execution-1' ] ,
330+ }
331+ )
332+
333+ const body = await response . json ( )
334+
335+ expect ( body . wrapper . nested ) . toEqual ( text )
336+ } )
337+
338+ it ( 'should memoize repeated materialized objects while resolving nested refs' , async ( ) => {
339+ const text = 'nested' . repeat ( 2 * 1024 * 1024 )
340+ const nestedOutput = await compactExecutionPayload (
341+ { text } ,
342+ {
343+ ...MATERIALIZATION_CONTEXT ,
344+ executionId : 'original-execution-1' ,
345+ requireDurable : true ,
346+ preserveRoot : true ,
347+ }
348+ )
349+ const nestedRef = ( nestedOutput as unknown as { text : unknown } ) . text
350+ const sourceValue = { nested : nestedRef }
351+ const sourceJson = JSON . stringify ( sourceValue )
352+ const sourceRef = await storeLargeValue (
353+ sourceValue ,
354+ sourceJson ,
355+ Buffer . byteLength ( sourceJson ) ,
356+ {
357+ ...MATERIALIZATION_CONTEXT ,
358+ executionId : 'source-execution-1' ,
359+ requireDurable : true ,
360+ }
361+ )
362+
363+ const response = await createHttpResponseFromBlock (
364+ buildExecutionResult ( {
365+ output : {
366+ data : { first : sourceRef , second : sourceRef } ,
367+ status : 200 ,
368+ headers : { } ,
369+ } ,
370+ } ) ,
371+ {
372+ ...MATERIALIZATION_CONTEXT ,
373+ largeValueKeys : sourceRef . key ? [ sourceRef . key ] : [ ] ,
374+ }
375+ )
376+
377+ const body = await response . json ( )
378+
379+ expect ( body ) . toEqual ( {
380+ first : { nested : text } ,
381+ second : { nested : text } ,
382+ } )
383+ } )
384+
168385 it ( 'should materialize large string refs for Response block HTTP output' , async ( ) => {
169386 const text = 'x' . repeat ( 9 * 1024 * 1024 )
170387 const output = await compactExecutionPayload (
0 commit comments