11/* eslint-disable sonarjs/no-duplicate-string */
2- import Cache from "@opennextjs/aws/adapters/cache.js" ;
2+ import Cache , { SOFT_TAG_PREFIX } from "@opennextjs/aws/adapters/cache.js" ;
33import { vi } from "vitest" ;
44
55declare global {
@@ -531,6 +531,8 @@ describe("CacheHandler", () => {
531531 await cache . revalidateTag ( "tag" ) ;
532532
533533 expect ( tagCache . writeTags ) . not . toHaveBeenCalled ( ) ;
534+ // Reset the config
535+ globalThis . openNextConfig . dangerous . disableTagCache = false ;
534536 } ) ;
535537
536538 it ( "Should call tagCache.writeTags" , async ( ) => {
@@ -551,13 +553,13 @@ describe("CacheHandler", () => {
551553 it ( "Should call invalidateCdnHandler.invalidatePaths" , async ( ) => {
552554 globalThis . tagCache . getByTag . mockResolvedValueOnce ( [ "/path" ] ) ;
553555 globalThis . tagCache . getByPath . mockResolvedValueOnce ( [ ] ) ;
554- await cache . revalidateTag ( "_N_T_/ path" ) ;
556+ await cache . revalidateTag ( ` ${ SOFT_TAG_PREFIX } path` ) ;
555557
556558 expect ( tagCache . writeTags ) . toHaveBeenCalledTimes ( 1 ) ;
557559 expect ( tagCache . writeTags ) . toHaveBeenCalledWith ( [
558560 {
559561 path : "/path" ,
560- tag : "_N_T_/ path" ,
562+ tag : ` ${ SOFT_TAG_PREFIX } path` ,
561563 } ,
562564 ] ) ;
563565
@@ -621,4 +623,199 @@ describe("CacheHandler", () => {
621623 globalThis . tagCache . getPathsByTags = undefined ;
622624 } ) ;
623625 } ) ;
626+
627+ describe ( "shouldBypassTagCache" , ( ) => {
628+ describe ( "fetch cache" , ( ) => {
629+ it ( "Should bypass tag cache validation when shouldBypassTagCache is true" , async ( ) => {
630+ incrementalCache . get . mockResolvedValueOnce ( {
631+ value : {
632+ kind : "FETCH" ,
633+ data : {
634+ headers : { } ,
635+ body : "{}" ,
636+ url : "https://example.com" ,
637+ status : 200 ,
638+ } ,
639+ } ,
640+ lastModified : Date . now ( ) ,
641+ shouldBypassTagCache : true ,
642+ } ) ;
643+
644+ const result = await cache . get ( "key" , {
645+ kind : "FETCH" ,
646+ tags : [ "tag1" ] ,
647+ } ) ;
648+
649+ expect ( getFetchCacheSpy ) . toHaveBeenCalled ( ) ;
650+ expect ( tagCache . getLastModified ) . not . toHaveBeenCalled ( ) ;
651+ expect ( tagCache . hasBeenRevalidated ) . not . toHaveBeenCalled ( ) ;
652+ expect ( result ) . not . toBeNull ( ) ;
653+ expect ( result ?. value ) . toEqual ( {
654+ kind : "FETCH" ,
655+ data : {
656+ headers : { } ,
657+ body : "{}" ,
658+ url : "https://example.com" ,
659+ status : 200 ,
660+ } ,
661+ } ) ;
662+ } ) ;
663+
664+ it ( "Should not bypass tag cache validation when shouldBypassTagCache is false" , async ( ) => {
665+ globalThis . tagCache . mode = "nextMode" ;
666+ incrementalCache . get . mockResolvedValueOnce ( {
667+ value : {
668+ kind : "FETCH" ,
669+ data : {
670+ headers : { } ,
671+ body : "{}" ,
672+ url : "https://example.com" ,
673+ status : 200 ,
674+ } ,
675+ } ,
676+ lastModified : Date . now ( ) ,
677+ shouldBypassTagCache : false ,
678+ } ) ;
679+
680+ const result = await cache . get ( "key" , {
681+ kind : "FETCH" ,
682+ tags : [ "tag1" ] ,
683+ } ) ;
684+
685+ expect ( getFetchCacheSpy ) . toHaveBeenCalled ( ) ;
686+ expect ( tagCache . hasBeenRevalidated ) . toHaveBeenCalled ( ) ;
687+ expect ( result ) . not . toBeNull ( ) ;
688+ } ) ;
689+
690+ it ( "Should not bypass tag cache validation when shouldBypassTagCache is undefined" , async ( ) => {
691+ globalThis . tagCache . mode = "nextMode" ;
692+ tagCache . hasBeenRevalidated . mockResolvedValueOnce ( false ) ;
693+ incrementalCache . get . mockResolvedValueOnce ( {
694+ value : {
695+ kind : "FETCH" ,
696+ data : {
697+ headers : { } ,
698+ body : "{}" ,
699+ url : "https://example.com" ,
700+ status : 200 ,
701+ } ,
702+ } ,
703+ lastModified : Date . now ( ) ,
704+ // shouldBypassTagCache not set
705+ } ) ;
706+
707+ const result = await cache . get ( "key" , {
708+ kind : "FETCH" ,
709+ tags : [ "tag1" ] ,
710+ } ) ;
711+
712+ expect ( getFetchCacheSpy ) . toHaveBeenCalled ( ) ;
713+ expect ( tagCache . hasBeenRevalidated ) . toHaveBeenCalled ( ) ;
714+ expect ( result ) . not . toBeNull ( ) ;
715+ } ) ;
716+
717+ it ( "Should bypass path validation when shouldBypassTagCache is true for soft tags" , async ( ) => {
718+ incrementalCache . get . mockResolvedValueOnce ( {
719+ value : {
720+ kind : "FETCH" ,
721+ data : {
722+ headers : { } ,
723+ body : "{}" ,
724+ url : "https://example.com" ,
725+ status : 200 ,
726+ } ,
727+ } ,
728+ lastModified : Date . now ( ) ,
729+ shouldBypassTagCache : true ,
730+ } ) ;
731+
732+ const result = await cache . get ( "key" , {
733+ kind : "FETCH" ,
734+ softTags : [ `${ SOFT_TAG_PREFIX } path` ] ,
735+ } ) ;
736+
737+ expect ( getFetchCacheSpy ) . toHaveBeenCalled ( ) ;
738+ expect ( tagCache . getLastModified ) . not . toHaveBeenCalled ( ) ;
739+ expect ( tagCache . hasBeenRevalidated ) . not . toHaveBeenCalled ( ) ;
740+ expect ( result ) . not . toBeNull ( ) ;
741+ } ) ;
742+ } ) ;
743+
744+ describe ( "incremental cache" , ( ) => {
745+ it ( "Should bypass tag cache validation when shouldBypassTagCache is true" , async ( ) => {
746+ incrementalCache . get . mockResolvedValueOnce ( {
747+ value : {
748+ type : "route" ,
749+ body : "{}" ,
750+ } ,
751+ lastModified : Date . now ( ) ,
752+ shouldBypassTagCache : true ,
753+ } ) ;
754+
755+ const result = await cache . get ( "key" , { kindHint : "app" } ) ;
756+
757+ expect ( getIncrementalCache ) . toHaveBeenCalled ( ) ;
758+ expect ( tagCache . getLastModified ) . not . toHaveBeenCalled ( ) ;
759+ expect ( tagCache . hasBeenRevalidated ) . not . toHaveBeenCalled ( ) ;
760+ expect ( result ) . not . toBeNull ( ) ;
761+ expect ( result ?. value ?. kind ) . toEqual ( "ROUTE" ) ;
762+ } ) ;
763+
764+ it ( "Should not bypass tag cache validation when shouldBypassTagCache is false" , async ( ) => {
765+ globalThis . tagCache . mode = "nextMode" ;
766+ incrementalCache . get . mockResolvedValueOnce ( {
767+ value : {
768+ type : "route" ,
769+ body : "{}" ,
770+ } ,
771+ lastModified : Date . now ( ) ,
772+ shouldBypassTagCache : false ,
773+ } ) ;
774+
775+ const result = await cache . get ( "key" , { kindHint : "app" } ) ;
776+
777+ expect ( getIncrementalCache ) . toHaveBeenCalled ( ) ;
778+ expect ( tagCache . hasBeenRevalidated ) . toHaveBeenCalled ( ) ;
779+ expect ( result ) . not . toBeNull ( ) ;
780+ } ) ;
781+
782+ it ( "Should return null when tag cache indicates revalidation and shouldBypassTagCache is false" , async ( ) => {
783+ globalThis . tagCache . mode = "nextMode" ;
784+ tagCache . hasBeenRevalidated . mockResolvedValueOnce ( true ) ;
785+ incrementalCache . get . mockResolvedValueOnce ( {
786+ value : {
787+ type : "route" ,
788+ body : "{}" ,
789+ } ,
790+ lastModified : Date . now ( ) ,
791+ shouldBypassTagCache : false ,
792+ } ) ;
793+
794+ const result = await cache . get ( "key" , { kindHint : "app" } ) ;
795+
796+ expect ( getIncrementalCache ) . toHaveBeenCalled ( ) ;
797+ expect ( tagCache . hasBeenRevalidated ) . toHaveBeenCalled ( ) ;
798+ expect ( result ) . toBeNull ( ) ;
799+ } ) ;
800+
801+ it ( "Should return value when tag cache indicates revalidation but shouldBypassTagCache is true" , async ( ) => {
802+ incrementalCache . get . mockResolvedValueOnce ( {
803+ value : {
804+ type : "route" ,
805+ body : "{}" ,
806+ } ,
807+ lastModified : Date . now ( ) ,
808+ shouldBypassTagCache : true ,
809+ } ) ;
810+
811+ const result = await cache . get ( "key" , { kindHint : "app" } ) ;
812+
813+ expect ( getIncrementalCache ) . toHaveBeenCalled ( ) ;
814+ expect ( tagCache . getLastModified ) . not . toHaveBeenCalled ( ) ;
815+ expect ( tagCache . hasBeenRevalidated ) . not . toHaveBeenCalled ( ) ;
816+ expect ( result ) . not . toBeNull ( ) ;
817+ expect ( result ?. value ?. kind ) . toEqual ( "ROUTE" ) ;
818+ } ) ;
819+ } ) ;
820+ } ) ;
624821} ) ;
0 commit comments