github.com/openfga/openfga@v1.5.4-rc1/pkg/storage/storagewrappers/caching_test.go (about) 1 package storagewrappers 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/oklog/ulid/v2" 10 openfgav1 "github.com/openfga/api/proto/openfga/v1" 11 "github.com/stretchr/testify/require" 12 "go.uber.org/goleak" 13 "go.uber.org/mock/gomock" 14 "golang.org/x/sync/errgroup" 15 16 "github.com/openfga/openfga/internal/mocks" 17 "github.com/openfga/openfga/pkg/typesystem" 18 ) 19 20 func TestReadAuthorizationModel(t *testing.T) { 21 ctx := context.Background() 22 t.Cleanup(func() { 23 goleak.VerifyNone(t) 24 }) 25 mockController := gomock.NewController(t) 26 mockController.Finish() 27 28 mockDatastore := mocks.NewMockOpenFGADatastore(mockController) 29 cachingBackend := NewCachedOpenFGADatastore(mockDatastore, 5) 30 t.Cleanup(cachingBackend.Close) 31 model := &openfgav1.AuthorizationModel{ 32 Id: ulid.Make().String(), 33 SchemaVersion: typesystem.SchemaVersion1_1, 34 TypeDefinitions: []*openfgav1.TypeDefinition{ 35 { 36 Type: "documents", 37 Relations: map[string]*openfgav1.Userset{ 38 "admin": typesystem.This(), 39 }, 40 }, 41 }, 42 } 43 storeID := ulid.Make().String() 44 gomock.InOrder( 45 mockDatastore.EXPECT().WriteAuthorizationModel(gomock.Any(), storeID, gomock.Any()).Times(1).Return(nil), 46 mockDatastore.EXPECT().ReadAuthorizationModel(gomock.Any(), storeID, model.GetId()).Times(1).Return(model, nil), 47 mockDatastore.EXPECT().FindLatestAuthorizationModel(gomock.Any(), storeID).Times(1).Return(model, nil), 48 mockDatastore.EXPECT().Close().Times(1), 49 ) 50 51 err := cachingBackend.WriteAuthorizationModel(ctx, storeID, model) 52 require.NoError(t, err) 53 54 // Check that first hit to cache -> miss. 55 gotModel, err := cachingBackend.ReadAuthorizationModel(ctx, storeID, model.GetId()) 56 require.NoError(t, err) 57 require.Equal(t, model, gotModel) 58 59 // Check what's stored inside the cache. 60 modelKey := fmt.Sprintf("%s:%s", storeID, model.GetId()) 61 cachedModel := cachingBackend.cache.Get(modelKey).Value() 62 require.Equal(t, model, cachedModel) 63 64 // Check that second hit to cache -> hit. 65 gotModel, err = cachingBackend.ReadAuthorizationModel(ctx, storeID, model.GetId()) 66 require.NoError(t, err) 67 require.Equal(t, model, gotModel) 68 69 // ensure find latest authorization model will get hte latest model 70 latestModel, err := cachingBackend.FindLatestAuthorizationModel(ctx, storeID) 71 require.NoError(t, err) 72 require.Equal(t, model, latestModel) 73 } 74 75 func TestSingleFlightFindLatestAuthorizationModel(t *testing.T) { 76 const numGoroutines = 2 77 78 t.Cleanup(func() { 79 goleak.VerifyNone(t) 80 }) 81 mockController := gomock.NewController(t) 82 mockController.Finish() 83 84 mockDatastore := mocks.NewMockOpenFGADatastore(mockController) 85 cachingBackend := NewCachedOpenFGADatastore(mockDatastore, 5) 86 t.Cleanup(cachingBackend.Close) 87 model := &openfgav1.AuthorizationModel{ 88 Id: ulid.Make().String(), 89 SchemaVersion: typesystem.SchemaVersion1_1, 90 TypeDefinitions: []*openfgav1.TypeDefinition{ 91 { 92 Type: "documents", 93 Relations: map[string]*openfgav1.Userset{ 94 "admin": typesystem.This(), 95 }, 96 }, 97 }, 98 } 99 100 storeID := ulid.Make().String() 101 gomock.InOrder( 102 mockDatastore.EXPECT().FindLatestAuthorizationModel(gomock.Any(), storeID).DoAndReturn( 103 func(ctx context.Context, storeID string) (*openfgav1.AuthorizationModel, error) { 104 time.Sleep(1 * time.Second) 105 return model, nil 106 }).Times(1), 107 mockDatastore.EXPECT().Close().Times(1), 108 ) 109 110 var wg errgroup.Group 111 for i := 0; i < numGoroutines; i++ { 112 wg.Go(func() error { 113 latestModel, err := cachingBackend.FindLatestAuthorizationModel(context.Background(), storeID) 114 if err != nil { 115 return err 116 } 117 require.NoError(t, err) 118 require.Equal(t, model, latestModel) 119 return nil 120 }) 121 } 122 err := wg.Wait() 123 require.NoError(t, err) 124 }