/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package cache import ( "sync" "testing" msp2 "github.com/hyperledger/fabric-protos-go/msp" "github.com/hyperledger/fabric/msp" "github.com/hyperledger/fabric/msp/mocks" "github.com/pkg/errors" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestNewCacheMsp(t *testing.T) { i, err := New(nil) require.Error(t, err) require.Nil(t, i) require.Contains(t, err.Error(), "Invalid passed MSP. It must be different from nil.") i, err = New(&mocks.MockMSP{}) require.NoError(t, err) require.NotNil(t, i) } func TestSetup(t *testing.T) { mockMSP := &mocks.MockMSP{} i, err := New(mockMSP) require.NoError(t, err) mockMSP.On("Setup", (*msp2.MSPConfig)(nil)).Return(nil) err = i.Setup(nil) require.NoError(t, err) mockMSP.AssertExpectations(t) require.Equal(t, 0, i.(*cachedMSP).deserializeIdentityCache.len()) require.Equal(t, 0, i.(*cachedMSP).satisfiesPrincipalCache.len()) require.Equal(t, 0, i.(*cachedMSP).validateIdentityCache.len()) } func TestGetType(t *testing.T) { mockMSP := &mocks.MockMSP{} i, err := New(mockMSP) require.NoError(t, err) mockMSP.On("GetType").Return(msp.FABRIC) require.Equal(t, msp.FABRIC, i.GetType()) mockMSP.AssertExpectations(t) } func TestGetIdentifier(t *testing.T) { mockMSP := &mocks.MockMSP{} i, err := New(mockMSP) require.NoError(t, err) mockMSP.On("GetIdentifier").Return("MSP", nil) id, err := i.GetIdentifier() require.NoError(t, err) require.Equal(t, "MSP", id) mockMSP.AssertExpectations(t) } func TestGetDefaultSigningIdentity(t *testing.T) { mockMSP := &mocks.MockMSP{} i, err := New(mockMSP) require.NoError(t, err) mockIdentity := &mocks.MockSigningIdentity{Mock: mock.Mock{}, MockIdentity: &mocks.MockIdentity{ID: "Alice"}} mockMSP.On("GetDefaultSigningIdentity").Return(mockIdentity, nil) id, err := i.GetDefaultSigningIdentity() require.NoError(t, err) require.Equal(t, mockIdentity, id) mockMSP.AssertExpectations(t) } func TestGetTLSRootCerts(t *testing.T) { mockMSP := &mocks.MockMSP{} i, err := New(mockMSP) require.NoError(t, err) expected := [][]byte{{1}, {2}} mockMSP.On("GetTLSRootCerts").Return(expected) certs := i.GetTLSRootCerts() require.Equal(t, expected, certs) } func TestGetTLSIntermediateCerts(t *testing.T) { mockMSP := &mocks.MockMSP{} i, err := New(mockMSP) require.NoError(t, err) expected := [][]byte{{1}, {2}} mockMSP.On("GetTLSIntermediateCerts").Return(expected) certs := i.GetTLSIntermediateCerts() require.Equal(t, expected, certs) } func TestDeserializeIdentity(t *testing.T) { mockMSP := &mocks.MockMSP{} wrappedMSP, err := New(mockMSP) require.NoError(t, err) // Check id is cached mockIdentity := &mocks.MockIdentity{ID: "Alice"} mockIdentity2 := &mocks.MockIdentity{ID: "Bob"} serializedIdentity := []byte{1, 2, 3} serializedIdentity2 := []byte{4, 5, 6} mockMSP.On("DeserializeIdentity", serializedIdentity).Return(mockIdentity, nil) mockMSP.On("DeserializeIdentity", serializedIdentity2).Return(mockIdentity2, nil) // Prime the cache wrappedMSP.DeserializeIdentity(serializedIdentity) wrappedMSP.DeserializeIdentity(serializedIdentity2) // Stress the cache and ensure concurrent operations // do not result in a failure var wg sync.WaitGroup wg.Add(100) for i := 0; i < 100; i++ { go func(m msp.MSP, i int) { sIdentity := serializedIdentity expectedIdentity := mockIdentity defer wg.Done() if i%2 == 0 { sIdentity = serializedIdentity2 expectedIdentity = mockIdentity2 } id, err := wrappedMSP.DeserializeIdentity(sIdentity) require.NoError(t, err) require.Equal(t, expectedIdentity, id.(*cachedIdentity).Identity) }(wrappedMSP, i) } wg.Wait() mockMSP.AssertExpectations(t) // Check the cache _, ok := wrappedMSP.(*cachedMSP).deserializeIdentityCache.get(string(serializedIdentity)) require.True(t, ok) // Check the same object is returned id, err := wrappedMSP.DeserializeIdentity(serializedIdentity) require.NoError(t, err) require.True(t, mockIdentity == id.(*cachedIdentity).Identity) mockMSP.AssertExpectations(t) // Check id is not cached mockIdentity = &mocks.MockIdentity{ID: "Bob"} serializedIdentity = []byte{1, 2, 3, 4} mockMSP.On("DeserializeIdentity", serializedIdentity).Return(mockIdentity, errors.New("Invalid identity")) _, err = wrappedMSP.DeserializeIdentity(serializedIdentity) require.Error(t, err) require.Contains(t, err.Error(), "Invalid identity") mockMSP.AssertExpectations(t) _, ok = wrappedMSP.(*cachedMSP).deserializeIdentityCache.get(string(serializedIdentity)) require.False(t, ok) } func TestValidate(t *testing.T) { mockMSP := &mocks.MockMSP{} i, err := New(mockMSP) require.NoError(t, err) // Check validation is cached mockIdentity := &mocks.MockIdentity{ID: "Alice"} mockIdentity.On("GetIdentifier").Return(&msp.IdentityIdentifier{Mspid: "MSP", Id: "Alice"}) mockMSP.On("Validate", mockIdentity).Return(nil) err = i.Validate(mockIdentity) require.NoError(t, err) mockIdentity.AssertExpectations(t) mockMSP.AssertExpectations(t) // Check the cache identifier := mockIdentity.GetIdentifier() key := string(identifier.Mspid + ":" + identifier.Id) v, ok := i.(*cachedMSP).validateIdentityCache.get(string(key)) require.True(t, ok) require.True(t, v.(bool)) // Recheck err = i.Validate(mockIdentity) require.NoError(t, err) // Check validation is not cached mockIdentity = &mocks.MockIdentity{ID: "Bob"} mockIdentity.On("GetIdentifier").Return(&msp.IdentityIdentifier{Mspid: "MSP", Id: "Bob"}) mockMSP.On("Validate", mockIdentity).Return(errors.New("Invalid identity")) err = i.Validate(mockIdentity) require.Error(t, err) mockIdentity.AssertExpectations(t) mockMSP.AssertExpectations(t) // Check the cache identifier = mockIdentity.GetIdentifier() key = string(identifier.Mspid + ":" + identifier.Id) _, ok = i.(*cachedMSP).validateIdentityCache.get(string(key)) require.False(t, ok) } func TestSatisfiesValidateIndirectCall(t *testing.T) { mockMSP := &mocks.MockMSP{} mockIdentity := &mocks.MockIdentity{ID: "Alice"} mockIdentity.On("Validate").Run(func(_ mock.Arguments) { panic("shouldn't have invoked the identity method") }) mockMSP.On("DeserializeIdentity", mock.Anything).Return(mockIdentity, nil).Once() mockIdentity.On("GetIdentifier").Return(&msp.IdentityIdentifier{Mspid: "MSP", Id: "Alice"}) cache, err := New(mockMSP) require.NoError(t, err) mockMSP.On("Validate", mockIdentity).Return(nil) // Test that cache returns the correct value, and also use this to prime the cache err = cache.Validate(mockIdentity) mockMSP.AssertNumberOfCalls(t, "Validate", 1) require.NoError(t, err) // Get the identity we test the caching on identity, err := cache.DeserializeIdentity([]byte{1, 2, 3}) require.NoError(t, err) // Ensure the identity returned answers what the cached MSP answers. err = identity.Validate() require.NoError(t, err) // Ensure that although a call to Validate was called, the calls weren't passed on to the backing MSP mockMSP.AssertNumberOfCalls(t, "Validate", 1) } func TestSatisfiesPrincipalIndirectCall(t *testing.T) { mockMSP := &mocks.MockMSP{} mockMSPPrincipal := &msp2.MSPPrincipal{PrincipalClassification: msp2.MSPPrincipal_IDENTITY, Principal: []byte{1, 2, 3}} mockIdentity := &mocks.MockIdentity{ID: "Alice"} mockIdentity.On("SatisfiesPrincipal", mockMSPPrincipal).Run(func(_ mock.Arguments) { panic("shouldn't have invoked the identity method") }) mockMSP.On("DeserializeIdentity", mock.Anything).Return(mockIdentity, nil).Once() mockIdentity.On("GetIdentifier").Return(&msp.IdentityIdentifier{Mspid: "MSP", Id: "Alice"}) cache, err := New(mockMSP) require.NoError(t, err) // First invocation of the SatisfiesPrincipal returns an error mockMSP.On("SatisfiesPrincipal", mockIdentity, mockMSPPrincipal).Return(errors.New("error: foo")).Once() // Second invocation returns nil mockMSP.On("SatisfiesPrincipal", mockIdentity, mockMSPPrincipal).Return(nil).Once() // Test that cache returns the correct value err = cache.SatisfiesPrincipal(mockIdentity, mockMSPPrincipal) require.Equal(t, "error: foo", err.Error()) // Get the identity we test the caching on identity, err := cache.DeserializeIdentity([]byte{1, 2, 3}) require.NoError(t, err) // Ensure the identity returned answers what the cached MSP answers. // If the invocation doesn't hit the cache, it will return nil instead of an error. err = identity.SatisfiesPrincipal(mockMSPPrincipal) require.Equal(t, "error: foo", err.Error()) } func TestSatisfiesPrincipal(t *testing.T) { mockMSP := &mocks.MockMSP{} i, err := New(mockMSP) require.NoError(t, err) // Check validation is cached mockIdentity := &mocks.MockIdentity{ID: "Alice"} mockIdentity.On("GetIdentifier").Return(&msp.IdentityIdentifier{Mspid: "MSP", Id: "Alice"}) mockMSPPrincipal := &msp2.MSPPrincipal{PrincipalClassification: msp2.MSPPrincipal_IDENTITY, Principal: []byte{1, 2, 3}} mockMSP.On("SatisfiesPrincipal", mockIdentity, mockMSPPrincipal).Return(nil) mockMSP.SatisfiesPrincipal(mockIdentity, mockMSPPrincipal) err = i.SatisfiesPrincipal(mockIdentity, mockMSPPrincipal) require.NoError(t, err) mockIdentity.AssertExpectations(t) mockMSP.AssertExpectations(t) // Check the cache identifier := mockIdentity.GetIdentifier() identityKey := string(identifier.Mspid + ":" + identifier.Id) principalKey := string(mockMSPPrincipal.PrincipalClassification) + string(mockMSPPrincipal.Principal) key := identityKey + principalKey v, ok := i.(*cachedMSP).satisfiesPrincipalCache.get(key) require.True(t, ok) require.Nil(t, v) // Recheck err = i.SatisfiesPrincipal(mockIdentity, mockMSPPrincipal) require.NoError(t, err) // Check validation is not cached mockIdentity = &mocks.MockIdentity{ID: "Bob"} mockIdentity.On("GetIdentifier").Return(&msp.IdentityIdentifier{Mspid: "MSP", Id: "Bob"}) mockMSPPrincipal = &msp2.MSPPrincipal{PrincipalClassification: msp2.MSPPrincipal_IDENTITY, Principal: []byte{1, 2, 3, 4}} mockMSP.On("SatisfiesPrincipal", mockIdentity, mockMSPPrincipal).Return(errors.New("Invalid")) mockMSP.SatisfiesPrincipal(mockIdentity, mockMSPPrincipal) err = i.SatisfiesPrincipal(mockIdentity, mockMSPPrincipal) require.Error(t, err) mockIdentity.AssertExpectations(t) mockMSP.AssertExpectations(t) // Check the cache identifier = mockIdentity.GetIdentifier() identityKey = string(identifier.Mspid + ":" + identifier.Id) principalKey = string(mockMSPPrincipal.PrincipalClassification) + string(mockMSPPrincipal.Principal) key = identityKey + principalKey v, ok = i.(*cachedMSP).satisfiesPrincipalCache.get(key) require.True(t, ok) require.NotNil(t, v) require.Contains(t, "Invalid", v.(error).Error()) }