@@ -511,19 +511,207 @@ public function test_perflab_aea_get_asset_size(): void {
511511 array (
512512 'url ' => 'https://example.com/script4.js ' ,
513513 'response ' => array (
514- 'code ' => 200 ,
515- 'body ' => str_repeat ( 'A ' , 1000 ),
514+ 'code ' => 200 ,
515+ 'body ' => str_repeat ( 'A ' , 1000 ),
516+ 'headers ' => array ( 'cache-control ' => 'max-age=3600 ' ),
516517 ),
517518 ),
518519 )
519520 );
520521
521522 $ this ->assertWPError ( perflab_aea_get_asset_size ( 'https://example.com/script1.js ' ) );
522523 $ this ->assertWPError ( perflab_aea_get_asset_size ( 'https://example.com/script2.js ' ) );
523- $ this ->assertEquals ( 0 , perflab_aea_get_asset_size ( 'https://example.com/script3.js ' ) );
524+ $ zero_size_result = perflab_aea_get_asset_size ( 'https://example.com/script3.js ' );
525+ $ this ->assertWPError ( $ zero_size_result );
526+ $ this ->assertSame ( 'zero_size ' , $ zero_size_result ->get_error_code () );
524527 $ this ->assertEquals ( 1000 , perflab_aea_get_asset_size ( 'https://example.com/script4.js ' ) );
525528 }
526529
530+ /**
531+ * Tests perflab_aea_get_asset_size() with non-cacheable responses.
532+ *
533+ * @covers ::perflab_aea_get_asset_size
534+ */
535+ public function test_perflab_aea_get_asset_size_not_cacheable (): void {
536+ Audit_Assets_Mock_Assets::clear_mocked ();
537+ Audit_Assets_Mock_Assets::mock_requests (
538+ array (
539+ array (
540+ 'url ' => 'https://example.com/no-store.js ' ,
541+ 'response ' => array (
542+ 'code ' => 200 ,
543+ 'body ' => str_repeat ( 'A ' , 500 ),
544+ 'headers ' => array ( 'cache-control ' => 'no-store ' ),
545+ ),
546+ ),
547+ array (
548+ 'url ' => 'https://example.com/no-store-with-extras.js ' ,
549+ 'response ' => array (
550+ 'code ' => 200 ,
551+ 'body ' => str_repeat ( 'A ' , 500 ),
552+ 'headers ' => array ( 'cache-control ' => 'no-store, must-revalidate ' ),
553+ ),
554+ ),
555+ array (
556+ 'url ' => 'https://example.com/no-cache.js ' ,
557+ 'response ' => array (
558+ 'code ' => 200 ,
559+ 'body ' => str_repeat ( 'A ' , 500 ),
560+ 'headers ' => array ( 'cache-control ' => 'no-cache ' ),
561+ ),
562+ ),
563+ array (
564+ 'url ' => 'https://example.com/max-age-zero.js ' ,
565+ 'response ' => array (
566+ 'code ' => 200 ,
567+ 'body ' => str_repeat ( 'A ' , 500 ),
568+ 'headers ' => array ( 'cache-control ' => 'max-age=0 ' ),
569+ ),
570+ ),
571+ array (
572+ 'url ' => 'https://example.com/cacheable.js ' ,
573+ 'response ' => array (
574+ 'code ' => 200 ,
575+ 'body ' => str_repeat ( 'A ' , 500 ),
576+ 'headers ' => array ( 'cache-control ' => 'max-age=3600 ' ),
577+ ),
578+ ),
579+ array (
580+ 'url ' => 'https://example.com/max-age-zero-quoted.js ' ,
581+ 'response ' => array (
582+ 'code ' => 200 ,
583+ 'body ' => str_repeat ( 'A ' , 500 ),
584+ 'headers ' => array ( 'cache-control ' => 'max-age="0" ' ),
585+ ),
586+ ),
587+ array (
588+ 'url ' => 'https://example.com/max-age-zero-spaced.js ' ,
589+ 'response ' => array (
590+ 'code ' => 200 ,
591+ 'body ' => str_repeat ( 'A ' , 500 ),
592+ 'headers ' => array ( 'cache-control ' => 'public, max-age = 0 ' ),
593+ ),
594+ ),
595+ array (
596+ 'url ' => 'https://example.com/no-store-array-header.js ' ,
597+ 'response ' => array (
598+ 'code ' => 200 ,
599+ 'body ' => str_repeat ( 'A ' , 500 ),
600+ 'headers ' => array ( 'cache-control ' => array ( 'no-store ' , 'must-revalidate ' ) ),
601+ ),
602+ ),
603+ array (
604+ 'url ' => 'https://example.com/expires-past.js ' ,
605+ 'response ' => array (
606+ 'code ' => 200 ,
607+ 'body ' => str_repeat ( 'A ' , 500 ),
608+ 'headers ' => array ( 'expires ' => 'Thu, 01 Jan 1970 00:00:00 GMT ' ),
609+ ),
610+ ),
611+ array (
612+ 'url ' => 'https://example.com/expires-future.js ' ,
613+ 'response ' => array (
614+ 'code ' => 200 ,
615+ 'body ' => str_repeat ( 'A ' , 500 ),
616+ 'headers ' => array ( 'expires ' => gmdate ( 'D, d M Y H:i:s ' , time () + 3600 ) . ' GMT ' ),
617+ ),
618+ ),
619+ array (
620+ 'url ' => 'https://example.com/expires-invalid.js ' ,
621+ 'response ' => array (
622+ 'code ' => 200 ,
623+ 'body ' => str_repeat ( 'A ' , 500 ),
624+ 'headers ' => array ( 'expires ' => '0 ' ),
625+ ),
626+ ),
627+ array (
628+ 'url ' => 'https://example.com/no-headers.js ' ,
629+ 'response ' => array (
630+ 'code ' => 200 ,
631+ 'body ' => str_repeat ( 'A ' , 500 ),
632+ 'headers ' => array (),
633+ ),
634+ ),
635+ array (
636+ 'url ' => 'https://example.com/last-modified-only.js ' ,
637+ 'response ' => array (
638+ 'code ' => 200 ,
639+ 'body ' => str_repeat ( 'A ' , 500 ),
640+ 'headers ' => array ( 'last-modified ' => 'Mon, 01 Jan 2024 00:00:00 GMT ' ),
641+ ),
642+ ),
643+ array (
644+ 'url ' => 'https://example.com/etag-only.js ' ,
645+ 'response ' => array (
646+ 'code ' => 200 ,
647+ 'body ' => str_repeat ( 'A ' , 500 ),
648+ 'headers ' => array ( 'etag ' => '"abc123" ' ),
649+ ),
650+ ),
651+ )
652+ );
653+
654+ $ no_store_result = perflab_aea_get_asset_size ( 'https://example.com/no-store.js ' );
655+ $ this ->assertWPError ( $ no_store_result );
656+ $ this ->assertSame ( 'not_cacheable ' , $ no_store_result ->get_error_code () );
657+
658+ $ no_store_extras_result = perflab_aea_get_asset_size ( 'https://example.com/no-store-with-extras.js ' );
659+ $ this ->assertWPError ( $ no_store_extras_result );
660+ $ this ->assertSame ( 'not_cacheable ' , $ no_store_extras_result ->get_error_code () );
661+
662+ // no-cache forces revalidation on every request and should be flagged.
663+ $ no_cache_result = perflab_aea_get_asset_size ( 'https://example.com/no-cache.js ' );
664+ $ this ->assertWPError ( $ no_cache_result );
665+ $ this ->assertSame ( 'not_cacheable ' , $ no_cache_result ->get_error_code () );
666+
667+ // max-age=0 forces revalidation on every request and should be flagged.
668+ $ max_age_zero_result = perflab_aea_get_asset_size ( 'https://example.com/max-age-zero.js ' );
669+ $ this ->assertWPError ( $ max_age_zero_result );
670+ $ this ->assertSame ( 'not_cacheable ' , $ max_age_zero_result ->get_error_code () );
671+
672+ // Normal cacheable response should return size.
673+ $ this ->assertEquals ( 500 , perflab_aea_get_asset_size ( 'https://example.com/cacheable.js ' ) );
674+
675+ // max-age with a quoted zero value should be flagged.
676+ $ max_age_zero_quoted_result = perflab_aea_get_asset_size ( 'https://example.com/max-age-zero-quoted.js ' );
677+ $ this ->assertWPError ( $ max_age_zero_quoted_result );
678+ $ this ->assertSame ( 'not_cacheable ' , $ max_age_zero_quoted_result ->get_error_code () );
679+
680+ // max-age with whitespace around the equals sign and a zero value should be flagged.
681+ $ max_age_zero_spaced_result = perflab_aea_get_asset_size ( 'https://example.com/max-age-zero-spaced.js ' );
682+ $ this ->assertWPError ( $ max_age_zero_spaced_result );
683+ $ this ->assertSame ( 'not_cacheable ' , $ max_age_zero_spaced_result ->get_error_code () );
684+
685+ // Cache-Control header returned as an array (multiple headers) should still be detected.
686+ $ no_store_array_result = perflab_aea_get_asset_size ( 'https://example.com/no-store-array-header.js ' );
687+ $ this ->assertWPError ( $ no_store_array_result );
688+ $ this ->assertSame ( 'not_cacheable ' , $ no_store_array_result ->get_error_code () );
689+
690+ // Expires in the past with no Cache-Control should be flagged.
691+ $ expires_past_result = perflab_aea_get_asset_size ( 'https://example.com/expires-past.js ' );
692+ $ this ->assertWPError ( $ expires_past_result );
693+ $ this ->assertSame ( 'not_cacheable ' , $ expires_past_result ->get_error_code () );
694+
695+ // Expires in the future with no Cache-Control should return size.
696+ $ this ->assertEquals ( 500 , perflab_aea_get_asset_size ( 'https://example.com/expires-future.js ' ) );
697+
698+ // An invalid Expires value is treated as already expired per RFC 7234 and should be flagged.
699+ $ expires_invalid_result = perflab_aea_get_asset_size ( 'https://example.com/expires-invalid.js ' );
700+ $ this ->assertWPError ( $ expires_invalid_result );
701+ $ this ->assertSame ( 'not_cacheable ' , $ expires_invalid_result ->get_error_code () );
702+
703+ // No Cache-Control, no Expires, and no validator should be flagged.
704+ $ no_headers_result = perflab_aea_get_asset_size ( 'https://example.com/no-headers.js ' );
705+ $ this ->assertWPError ( $ no_headers_result );
706+ $ this ->assertSame ( 'not_cacheable ' , $ no_headers_result ->get_error_code () );
707+
708+ // No Cache-Control and no Expires but a Last-Modified validator allows heuristic caching, so it should return size.
709+ $ this ->assertEquals ( 500 , perflab_aea_get_asset_size ( 'https://example.com/last-modified-only.js ' ) );
710+
711+ // No Cache-Control and no Expires but an ETag validator allows heuristic caching, so it should return size.
712+ $ this ->assertEquals ( 500 , perflab_aea_get_asset_size ( 'https://example.com/etag-only.js ' ) );
713+ }
714+
527715 /**
528716 * Tests perflab_aea_copy_basic_auth_headers() with various scenarios.
529717 *
0 commit comments