Skip to content

Commit

Permalink
feat: Add unit tests for new functions
Browse files Browse the repository at this point in the history
Relates to edgexfoundry#5038. Add unit tests on the controller and app layers.

Signed-off-by: Lindsey Cheng <[email protected]>
  • Loading branch information
lindseysimple committed Dec 30, 2024
1 parent b41f93f commit 48ba4ff
Show file tree
Hide file tree
Showing 14 changed files with 684 additions and 30 deletions.
10 changes: 6 additions & 4 deletions internal/pkg/utils/aes.go → internal/pkg/utils/crypto/aes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// SPDX-License-Identifier: Apache-2.0

package utils
package crypto

import (
"bytes"
Expand All @@ -13,6 +13,8 @@ import (
"encoding/base64"
"io"

"github.com/edgexfoundry/edgex-go/internal/pkg/utils/crypto/interfaces"

"github.com/edgexfoundry/go-mod-core-contracts/v4/errors"
)

Expand All @@ -23,14 +25,14 @@ type AESCryptor struct {
key []byte
}

func NewAESCryptor() *AESCryptor {
func NewAESCryptor() interfaces.Crypto {
return &AESCryptor{
key: []byte(aesKey),
}
}

// Encrypt encrypts the given plaintext with AES-CBC mode and returns a string in base64 encoding
func (c AESCryptor) Encrypt(plaintext string) (string, errors.EdgeX) {
func (c *AESCryptor) Encrypt(plaintext string) (string, errors.EdgeX) {
bytePlaintext := []byte(plaintext)
block, err := aes.NewCipher(c.key)
if err != nil {
Expand All @@ -54,7 +56,7 @@ func (c AESCryptor) Encrypt(plaintext string) (string, errors.EdgeX) {
}

// Decrypt decrypts the given ciphertext with AES-CBC mode and returns the original value as string
func (c AESCryptor) Decrypt(ciphertext string) ([]byte, errors.EdgeX) {
func (c *AESCryptor) Decrypt(ciphertext string) ([]byte, errors.EdgeX) {
decodedCipherText, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return nil, errors.NewCommonEdgeX(errors.KindServerError, "decrypt failed", err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// SPDX-License-Identifier: Apache-2.0

package utils
package crypto

import (
"testing"
Expand Down
13 changes: 13 additions & 0 deletions internal/pkg/utils/crypto/interfaces/crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Copyright (C) 2024 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package interfaces

import "github.com/edgexfoundry/go-mod-core-contracts/v4/errors"

type Crypto interface {
Encrypt(string) (string, errors.EdgeX)
Decrypt(string) ([]byte, errors.EdgeX)
}
90 changes: 90 additions & 0 deletions internal/pkg/utils/crypto/interfaces/mocks/Crypto.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions internal/security/proxyauth/application/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ package application

import (
"fmt"

"github.com/edgexfoundry/edgex-go/internal/pkg/utils"
"github.com/edgexfoundry/edgex-go/internal/security/proxyauth/container"
proxyAuthUtils "github.com/edgexfoundry/edgex-go/internal/security/proxyauth/utils"

"github.com/edgexfoundry/go-mod-bootstrap/v4/di"
"github.com/edgexfoundry/go-mod-core-contracts/v4/common"
"github.com/edgexfoundry/go-mod-core-contracts/v4/dtos"
Expand All @@ -23,6 +20,7 @@ import (
// and then invokes AddKey function of infrastructure layer to add new user
func AddKey(dic *di.Container, keyData models.KeyData) errors.EdgeX {
dbClient := container.DBClientFrom(dic.Get)
cryptor := container.CryptoFrom(dic.Get)

keyName := ""
if len(keyData.Type) == 0 {
Expand All @@ -39,7 +37,7 @@ func AddKey(dic *di.Container, keyData models.KeyData) errors.EdgeX {
fmt.Sprintf("key type should be one of the '%s' or '%s'", common.VerificationKeyType, common.SigningKeyType), nil)
}

encryptedKey, err := utils.NewAESCryptor().Encrypt(keyData.Key)
encryptedKey, err := cryptor.Encrypt(keyData.Key)
if err != nil {
return errors.NewCommonEdgeX(errors.Kind(err), "failed to encrypt the key", err)
}
Expand Down Expand Up @@ -69,12 +67,14 @@ func VerificationKeyByIssuer(dic *di.Container, issuer string) (dtos.KeyData, er
}
keyName := proxyAuthUtils.VerificationKeyName(issuer)
dbClient := container.DBClientFrom(dic.Get)
cryptor := container.CryptoFrom(dic.Get)

keyData, err := dbClient.ReadKeyContent(keyName)
if err != nil {
return dtos.KeyData{}, errors.NewCommonEdgeXWrapper(err)
}
decryptedKey, err := utils.NewAESCryptor().Decrypt(keyData)

decryptedKey, err := cryptor.Decrypt(keyData)
if err != nil {
return dtos.KeyData{}, errors.NewCommonEdgeX(errors.Kind(err), "failed to decrypt the key", err)
}
Expand Down
172 changes: 172 additions & 0 deletions internal/security/proxyauth/application/key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//
// Copyright (C) 2024 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package application

import (
"testing"

cryptoMocks "github.com/edgexfoundry/edgex-go/internal/pkg/utils/crypto/interfaces/mocks"
"github.com/edgexfoundry/edgex-go/internal/security/proxyauth/container"
"github.com/edgexfoundry/edgex-go/internal/security/proxyauth/infrastructure/interfaces/mocks"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v4/di"
"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v4/common"
"github.com/edgexfoundry/go-mod-core-contracts/v4/dtos"
"github.com/edgexfoundry/go-mod-core-contracts/v4/errors"
"github.com/edgexfoundry/go-mod-core-contracts/v4/models"

"github.com/stretchr/testify/require"
)

func mockDic() *di.Container {
return di.NewContainer(di.ServiceConstructorMap{
bootstrapContainer.LoggingClientInterfaceName: func(get di.Get) interface{} {
return logger.NewMockClient()
},
})
}

func TestAddKey(t *testing.T) {
dic := mockDic()

validNewKey := "validNewKey"
validIssuer := "testIssuer"
validKeyData := models.KeyData{
Type: common.VerificationKeyType,
Issuer: validIssuer,
Key: validNewKey,
}
validKeyName := validKeyData.Issuer + "/" + validKeyData.Type
validEncryptedKey := "encryptedValidNewKey"

validUpdateKey := "validUpdateKey"
updateKeyData := models.KeyData{
Type: common.SigningKeyType,
Issuer: "issuer2",
Key: validUpdateKey,
}
validUpdateKeyName := updateKeyData.Issuer + "/" + updateKeyData.Type
validUpdateEncryptedKey := "encryptedValidUpdateKey"

invalidKeyData := models.KeyData{
Type: "invalidKeyType",
Issuer: "issuer2",
Key: validUpdateKey,
}

encryptFailedKey := "encryptFailedKey"
encryptFailedKeyData := models.KeyData{
Type: common.SigningKeyType,
Issuer: "issuer3",
Key: encryptFailedKey,
}

dbClientMock := &mocks.DBClient{}
dbClientMock.On("KeyExists", validKeyName).Return(false, nil)
dbClientMock.On("AddKey", validKeyName, validEncryptedKey).Return(nil)
dbClientMock.On("KeyExists", validUpdateKeyName).Return(true, nil)
dbClientMock.On("UpdateKey", validUpdateKeyName, validUpdateEncryptedKey).Return(nil)

cryptoMock := &cryptoMocks.Crypto{}
cryptoMock.On("Encrypt", validKeyData.Key).Return(validEncryptedKey, nil)
cryptoMock.On("Encrypt", updateKeyData.Key).Return(validUpdateEncryptedKey, nil)
cryptoMock.On("Encrypt", encryptFailedKeyData.Key).Return("", errors.NewCommonEdgeX(errors.KindServerError, "failed to encrypt the key", nil))

dic.Update(di.ServiceConstructorMap{
container.DBClientInterfaceName: func(get di.Get) interface{} {
return dbClientMock
},
container.CryptoInterfaceName: func(get di.Get) interface{} {
return cryptoMock
},
})

tests := []struct {
name string
keyData models.KeyData
errorExpected bool
errKind errors.ErrKind
}{
{"Valid - Add new verification key", validKeyData, false, ""},
{"Valid - Update existing signing key", updateKeyData, false, ""},
{"Invalid - Invalid key type", invalidKeyData, true, errors.KindContractInvalid},
{"Invalid - Encryption Error", encryptFailedKeyData, true, errors.KindServerError},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := AddKey(dic, test.keyData)
if test.errorExpected {
require.Error(t, err)
require.Equal(t, test.errKind, errors.Kind(err))
} else {
require.NoError(t, err)
}
})
}
}

func TestVerificationKeyByIssuer(t *testing.T) {
dic := mockDic()

validIssuer := "issuer1"
validEncryptedKey := "encryptedKey"
expectedKeyName := validIssuer + "/" + common.VerificationKeyType
expectedKeyData := dtos.KeyData{Issuer: validIssuer, Type: common.VerificationKeyType, Key: "decryptedKey"}

invalidIssuer := "invalidIssuer"
invalidKeyName := invalidIssuer + "/" + common.VerificationKeyType

decryptErrIssuer := "decryptErrIssuer"
decryptErrKeyName := decryptErrIssuer + "/" + common.VerificationKeyType
decryptErrKey := "decryptErrKey"

dbClientMock := &mocks.DBClient{}
dbClientMock.On("ReadKeyContent", expectedKeyName).Return(validEncryptedKey, nil)
dbClientMock.On("ReadKeyContent", invalidKeyName).Return("", errors.NewCommonEdgeX(errors.KindServerError, "read key error", nil))
dbClientMock.On("ReadKeyContent", decryptErrKeyName).Return(decryptErrKey, nil)

cryptoMock := &cryptoMocks.Crypto{}
cryptoMock.On("Decrypt", validEncryptedKey).Return([]byte("decryptedKey"), nil)
cryptoMock.On("Decrypt", decryptErrKey).Return([]byte{}, errors.NewCommonEdgeX(errors.KindServerError, "decrypt key error", nil))

dic.Update(di.ServiceConstructorMap{
container.DBClientInterfaceName: func(get di.Get) interface{} {
return dbClientMock
},
container.CryptoInterfaceName: func(get di.Get) interface{} {
return cryptoMock
},
})

tests := []struct {
name string
issuer string
expectedKeyData dtos.KeyData
errorExpected bool
errKind errors.ErrKind
}{
{"Valid - Valid key", validIssuer, expectedKeyData, false, ""},
{"Invalid - Empty issuer", "", dtos.KeyData{}, true, errors.KindContractInvalid},
{"Invalid - Key read error", invalidIssuer, dtos.KeyData{}, true, errors.KindServerError},
{"Invalid - Decryption error", decryptErrIssuer, dtos.KeyData{}, true, errors.KindServerError},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result, err := VerificationKeyByIssuer(dic, test.issuer)
if test.errorExpected {
require.Error(t, err)
require.Equal(t, test.errKind, errors.Kind(err))
} else {
require.NoError(t, err)
require.Equal(t, test.expectedKeyData, result)
}
})
}
}
20 changes: 20 additions & 0 deletions internal/security/proxyauth/container/cryptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Copyright (C) 2024 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package container

import (
"github.com/edgexfoundry/edgex-go/internal/pkg/utils/crypto/interfaces"

"github.com/edgexfoundry/go-mod-bootstrap/v4/di"
)

// CryptoInterfaceName contains the name of the interfaces.Crypto implementation in the DIC.
var CryptoInterfaceName = di.TypeInstanceToName((*interfaces.Crypto)(nil))

// CryptoFrom helper function queries the DIC and returns the interfaces.Cryptor implementation.
func CryptoFrom(get di.Get) interfaces.Crypto {
return get(CryptoInterfaceName).(interfaces.Crypto)
}
Loading

0 comments on commit 48ba4ff

Please sign in to comment.