@@ -473,15 +473,21 @@ describe('Asset Scan Status – Download Error Handling (§ 3.3)', () => {
473473 // NOT replace it with "Session timed out" or a generic SDK message.
474474
475475 // Use an invalid URL to trigger a predictable SDK error and verify propagation
476+ let errorThrown = false
476477 try {
477- await stack . asset ( ) . download ( { url : 'https://invalid-host.example.com/nonexistent-asset' , responseType : 'blob ' } )
478+ await stack . asset ( ) . download ( { url : 'https://invalid-host.example.com/nonexistent-asset' , responseType : 'arraybuffer ' } )
478479 } catch ( err ) {
480+ errorThrown = true
479481 // Must expose the real error — not hide it behind a generic wrapper
480482 expect ( err . message || err . errorMessage || err . code ) . to . not . equal ( undefined ,
481483 'SDK must surface download errors — error message must not be empty' )
482- expect ( err . message ) . to . not . include ( 'Session timed out' ,
483- 'SDK must not replace real download errors with generic session timeout message' )
484+ if ( err . message ) {
485+ expect ( err . message ) . to . not . include ( 'Session timed out' ,
486+ 'SDK must not replace real download errors with generic session timeout message' )
487+ }
484488 }
489+ expect ( errorThrown ) . to . equal ( true ,
490+ 'download() must throw for an invalid host URL — SDK must not silently succeed' )
485491 } )
486492
487493 it ( 'SDK asset.download() must propagate 422 status for scan-blocked downloads' , async function ( ) {
@@ -493,6 +499,7 @@ describe('Asset Scan Status – Download Error Handling (§ 3.3)', () => {
493499 // Fetch a real asset then attempt to call download with an injected bad URL
494500 // to confirm the SDK propagates the error with a proper status code.
495501 let assetUid
502+ let errorWasThrown = false
496503 try {
497504 const uploadResp = await stack . asset ( ) . create ( {
498505 upload : assetPath ,
@@ -504,6 +511,7 @@ describe('Asset Scan Status – Download Error Handling (§ 3.3)', () => {
504511 const assetObj = await stack . asset ( assetUid ) . fetch ( )
505512 await assetObj . download ( { url : assetObj . url + '?__scan_error_test=1' , responseType : 'arraybuffer' } )
506513 } catch ( err ) {
514+ errorWasThrown = true
507515 // SDK must not swallow the status code
508516 if ( err . status !== undefined ) {
509517 expect ( err . status ) . to . be . a ( 'number' ,
@@ -517,6 +525,9 @@ describe('Asset Scan Status – Download Error Handling (§ 3.3)', () => {
517525 try { await stack . asset ( assetUid ) . delete ( ) } catch ( e ) { /* ignore */ }
518526 }
519527 }
528+ // CDN may serve the asset despite the injected query param — explicitly skip
529+ // rather than silently pass so the 422-propagation contract is clearly not exercised.
530+ if ( ! errorWasThrown ) this . skip ( )
520531 } )
521532} )
522533
@@ -535,6 +546,12 @@ describe('Asset Scan Status – Scan Lifecycle (clean + quarantined)', () => {
535546 before ( function ( ) {
536547 const apiKey = process . env . API_KEY
537548 if ( ! apiKey ) return this . skip ( )
549+ // Gate behind explicit opt-in: EICAR upload can trigger AV controls in CI environments.
550+ // Set SCAN_LIFECYCLE_TESTS_ENABLED=true in the pipeline/env to enable this suite.
551+ if ( ! process . env . SCAN_LIFECYCLE_TESTS_ENABLED ) {
552+ console . log ( ' [scan-test] Lifecycle tests skipped — set SCAN_LIFECYCLE_TESTS_ENABLED=true to enable' )
553+ return this . skip ( )
554+ }
538555 stack = buildStack ( apiKey )
539556 } )
540557
0 commit comments