@@ -34,6 +34,7 @@ const API_KEY_ENV_VARS = [
3434export interface E2ETestState {
3535 tempCacheDir : string
3636 initialCacheHash : string
37+ initialCacheFiles : Set < string >
3738 /** Whether cache updates are allowed (UPDATE_E2E_CACHE=true) */
3839 allowCacheUpdates : boolean
3940 /** Whether cache fixture exists */
@@ -44,6 +45,7 @@ export interface E2ETestState {
4445export let testState : E2ETestState = {
4546 tempCacheDir : '' ,
4647 initialCacheHash : '' ,
48+ initialCacheFiles : new Set ( ) ,
4749 allowCacheUpdates : false ,
4850 hasFixture : false
4951}
@@ -54,29 +56,37 @@ export function setTestState(state: E2ETestState): void {
5456}
5557
5658/**
57- * Calculate SHA256 hash of all files in a directory ( recursively)
59+ * Collect all files in a directory recursively.
5860 */
59- function hashDirectory ( dir : string ) : string {
60- if ( ! existsSync ( dir ) ) return ''
61+ function collectFilesRecursive ( dir : string ) : string [ ] {
62+ if ( ! existsSync ( dir ) ) return [ ]
6163
62- const hash = createHash ( 'sha256' )
6364 const files : string [ ] = [ ]
6465
65- function collectFiles ( currentDir : string ) : void {
66+ function walk ( currentDir : string ) : void {
6667 const entries = readdirSync ( currentDir , { withFileTypes : true } )
6768 for ( const entry of entries ) {
6869 const fullPath = join ( currentDir , entry . name )
6970 if ( entry . isDirectory ( ) ) {
70- collectFiles ( fullPath )
71+ walk ( fullPath )
7172 } else {
7273 files . push ( fullPath )
7374 }
7475 }
7576 }
7677
77- collectFiles ( dir )
78- files . sort ( )
78+ walk ( dir )
79+ return files . sort ( )
80+ }
81+
82+ /**
83+ * Calculate SHA256 hash of all files in a directory (recursively)
84+ */
85+ function hashDirectory ( dir : string ) : string {
86+ const files = collectFilesRecursive ( dir )
87+ if ( files . length === 0 ) return ''
7988
89+ const hash = createHash ( 'sha256' )
8090 for ( const file of files ) {
8191 const content = readFileSync ( file , 'utf-8' )
8292 hash . update ( file . replace ( dir , '' ) )
@@ -86,6 +96,20 @@ function hashDirectory(dir: string): string {
8696 return hash . digest ( 'hex' )
8797}
8898
99+ /**
100+ * List files in cache directories (relative paths).
101+ */
102+ function listCacheFiles ( cacheDir : string ) : Set < string > {
103+ const files = new Set < string > ( )
104+ for ( const subdir of [ 'requests' , 'images' ] ) {
105+ const dir = join ( cacheDir , subdir )
106+ for ( const file of collectFilesRecursive ( dir ) ) {
107+ files . add ( file . replace ( `${ cacheDir } /` , '' ) )
108+ }
109+ }
110+ return files
111+ }
112+
89113/**
90114 * Extract cache fixture to temp directory
91115 */
@@ -292,8 +316,9 @@ export function setupE2ETests(): E2ETestState {
292316
293317 extractCacheFixture ( tempCacheDir )
294318 const initialCacheHash = hashCacheDirectories ( tempCacheDir )
319+ const initialCacheFiles = listCacheFiles ( tempCacheDir )
295320
296- return { tempCacheDir, initialCacheHash, allowCacheUpdates, hasFixture }
321+ return { tempCacheDir, initialCacheHash, initialCacheFiles , allowCacheUpdates, hasFixture }
297322}
298323
299324/**
@@ -327,9 +352,36 @@ export function teardownE2ETests(state: E2ETestState): void {
327352 finalCacheHash !== state . initialCacheHash
328353 ) {
329354 // This should NOT happen in locked mode - it means HTTP guard failed
355+ const finalCacheFiles = listCacheFiles ( state . tempCacheDir )
356+ const added = [ ...finalCacheFiles ] . filter ( ( f ) => ! state . initialCacheFiles . has ( f ) )
357+ const removed = [ ...state . initialCacheFiles ] . filter ( ( f ) => ! finalCacheFiles . has ( f ) )
358+
359+ console . error ( '' )
330360 console . error ( '❌ ERROR: Cache was modified in LOCKED mode!' )
331361 console . error ( ' This indicates uncached HTTP requests were made.' )
332- console . error ( ' Check E2E_CACHE_LOCKED handling in src/http.ts' )
362+ console . error ( '' )
363+ if ( added . length > 0 ) {
364+ console . error ( ' Files added:' )
365+ for ( const f of added . slice ( 0 , 20 ) ) {
366+ console . error ( ` + ${ f } ` )
367+ }
368+ if ( added . length > 20 ) {
369+ console . error ( ` ... and ${ added . length - 20 } more` )
370+ }
371+ }
372+ if ( removed . length > 0 ) {
373+ console . error ( ' Files removed:' )
374+ for ( const f of removed . slice ( 0 , 20 ) ) {
375+ console . error ( ` - ${ f } ` )
376+ }
377+ if ( removed . length > 20 ) {
378+ console . error ( ` ... and ${ removed . length - 20 } more` )
379+ }
380+ }
381+ console . error ( '' )
382+ console . error ( ' To update the cache fixture, run:' )
383+ console . error ( ' UPDATE_E2E_CACHE=true bun run test:e2e' )
384+ console . error ( '' )
333385 }
334386
335387 rmSync ( state . tempCacheDir , { recursive : true , force : true } )
0 commit comments