@@ -661,6 +661,98 @@ func TestWhatCanTargetAccess_MetricsRecorded_CacheHit(t *testing.T) {
661661 require .Equal (t , int64 (2 ), agg .SumResultSize )
662662}
663663
664+ func TestCheck_MetricsRecorded_FullCacheHit (t * testing.T ) {
665+ ac , mockSDK , mockCache , collector := injectAuthzMocksWithCollector (t )
666+ ctx := cctx .AddProjectID (context .TODO (), "proj1" )
667+ relations := []* descope.FGARelation {
668+ {Resource : "mario" , Target : "luigi" , Relation : "bigBro" },
669+ {Resource : "luigi" , Target : "mario" , Relation : "bigBro" },
670+ }
671+ mockSDK .MockFGA .CheckAssert = func (_ []* descope.FGARelation ) {
672+ require .Fail (t , "should not be called on full cache hit" )
673+ }
674+ mockCache .CheckRelationFunc = func (_ context.Context , _ * descope.FGARelation ) (bool , bool , bool ) {
675+ return true , true , true
676+ }
677+ mockCache .UpdateCacheWithChecksFunc = func (_ context.Context , _ []* descope.FGACheck ) {
678+ require .Fail (t , "should not be called on full cache hit" )
679+ }
680+ _ , err := ac .Check (ctx , relations )
681+ require .NoError (t , err )
682+ snapshot := collector .SnapshotAndReset ()
683+ require .Contains (t , snapshot , "proj1" )
684+ agg := snapshot ["proj1" ][metrics .APICheck ]
685+ require .NotNil (t , agg )
686+ require .Equal (t , int64 (1 ), agg .TotalCalls )
687+ require .Equal (t , int64 (1 ), agg .HitCount )
688+ require .Equal (t , int64 (0 ), agg .MissCount )
689+ require .Equal (t , int64 (0 ), agg .SumCandidates ) // full hit: nothing sent to SDK
690+ require .Equal (t , int64 (2 ), agg .SumResultSize )
691+ }
692+
693+ func TestCheck_MetricsRecorded_FullCacheMiss (t * testing.T ) {
694+ ac , mockSDK , mockCache , collector := injectAuthzMocksWithCollector (t )
695+ ctx := cctx .AddProjectID (context .TODO (), "proj1" )
696+ relations := []* descope.FGARelation {
697+ {Resource : "mario" , Target : "luigi" , Relation : "bigBro" },
698+ {Resource : "luigi" , Target : "mario" , Relation : "bigBro" },
699+ }
700+ sdkResponse := []* descope.FGACheck {
701+ {Allowed : true , Relation : relations [0 ], Info : & descope.FGACheckInfo {Direct : true }},
702+ {Allowed : false , Relation : relations [1 ], Info : & descope.FGACheckInfo {Direct : true }},
703+ }
704+ mockCache .CheckRelationFunc = func (_ context.Context , _ * descope.FGARelation ) (bool , bool , bool ) {
705+ return false , false , false
706+ }
707+ mockSDK .MockFGA .CheckResponse = sdkResponse
708+ mockCache .UpdateCacheWithChecksFunc = func (_ context.Context , _ []* descope.FGACheck ) {}
709+ _ , err := ac .Check (ctx , relations )
710+ require .NoError (t , err )
711+ snapshot := collector .SnapshotAndReset ()
712+ require .Contains (t , snapshot , "proj1" )
713+ agg := snapshot ["proj1" ][metrics .APICheck ]
714+ require .NotNil (t , agg )
715+ require .Equal (t , int64 (1 ), agg .TotalCalls )
716+ require .Equal (t , int64 (0 ), agg .HitCount )
717+ require .Equal (t , int64 (1 ), agg .MissCount )
718+ require .Equal (t , int64 (2 ), agg .SumCandidates ) // full miss: all 2 relations sent to SDK
719+ require .Equal (t , int64 (2 ), agg .SumResultSize )
720+ }
721+
722+ func TestCheck_MetricsRecorded_PartialHit (t * testing.T ) {
723+ ac , mockSDK , mockCache , collector := injectAuthzMocksWithCollector (t )
724+ ctx := cctx .AddProjectID (context .TODO (), "proj1" )
725+ relations := []* descope.FGARelation {
726+ {Resource : "mario" , Target : "luigi" , Relation : "bigBro" },
727+ {Resource : "luigi" , Target : "mario" , Relation : "bigBro" }, // only this one in cache
728+ {Resource : "mario" , Target : "bowser" , Relation : "enemy" },
729+ }
730+ sdkResponse := []* descope.FGACheck {
731+ {Allowed : true , Relation : relations [0 ], Info : & descope.FGACheckInfo {Direct : true }},
732+ {Allowed : true , Relation : relations [2 ], Info : & descope.FGACheckInfo {Direct : true }},
733+ }
734+ mockCache .CheckRelationFunc = func (_ context.Context , r * descope.FGARelation ) (bool , bool , bool ) {
735+ if r .Resource == "luigi" && r .Target == "mario" {
736+ return false , true , true // cached
737+ }
738+ return false , false , false // not cached
739+ }
740+ mockSDK .MockFGA .CheckResponse = sdkResponse
741+ mockCache .UpdateCacheWithChecksFunc = func (_ context.Context , _ []* descope.FGACheck ) {}
742+ _ , err := ac .Check (ctx , relations )
743+ require .NoError (t , err )
744+ snapshot := collector .SnapshotAndReset ()
745+ require .Contains (t , snapshot , "proj1" )
746+ agg := snapshot ["proj1" ][metrics .APICheck ]
747+ require .NotNil (t , agg )
748+ require .Equal (t , int64 (1 ), agg .TotalCalls )
749+ require .Equal (t , int64 (0 ), agg .HitCount )
750+ require .Equal (t , int64 (1 ), agg .MissCount )
751+ require .Equal (t , int64 (2 ), agg .SumCandidates ) // 2 relations sent to SDK (1 was cached)
752+ require .Equal (t , int64 (0 ), agg .SumFiltered )
753+ require .Equal (t , int64 (3 ), agg .SumResultSize )
754+ }
755+
664756func BenchmarkCheck (b * testing.B ) {
665757 for _ , numRelations := range []int {500 , 1000 , 5000 } {
666758 name := fmt .Sprintf ("relations=%d" , numRelations )
0 commit comments