Skip to content

Commit

Permalink
test: add unit tests for SDK (#900)
Browse files Browse the repository at this point in the history
* refactor: remove v2 naming

Signed-off-by: Chris Hung <chris@iotechsys.com>

* test: add unit tests for application package

Signed-off-by: Chris Hung <chris@iotechsys.com>

* test: complement unit tests for CommandValue

Signed-off-by: Chris Hung <chris@iotechsys.com>

* test: complement unit tests for transformer package

Signed-off-by: Chris Hung <chris@iotechsys.com>

Co-authored-by: Cloud Tsai <cloudxxx8@gmail.com>
  • Loading branch information
Chris Hung and cloudxxx8 authored May 10, 2021
1 parent edddd13 commit c59eaec
Show file tree
Hide file tree
Showing 6 changed files with 1,135 additions and 6 deletions.
289 changes: 289 additions & 0 deletions internal/application/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
//
// Copyright (C) 2021 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package application

import (
"context"
"testing"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v2/di"
"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"
clientMocks "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/clients/interfaces/mocks"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos/common"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos/responses"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models"
"github.com/google/uuid"
"github.com/stretchr/testify/require"

"github.com/edgexfoundry/device-sdk-go/v2/internal/cache"
"github.com/edgexfoundry/device-sdk-go/v2/internal/config"
"github.com/edgexfoundry/device-sdk-go/v2/internal/container"
sdkModels "github.com/edgexfoundry/device-sdk-go/v2/pkg/models"
"github.com/edgexfoundry/device-sdk-go/v2/pkg/models/mocks"
)

var testProtocols map[string]models.ProtocolProperties

var testDevice = models.Device{
Name: "test-device",
AdminState: models.Unlocked,
OperatingState: models.Up,
Protocols: testProtocols,
ServiceName: "test-service",
ProfileName: "test-profile",
}

func mockDic() *di.Container {
driverMock := &mocks.ProtocolDriver{}
cr := sdkModels.CommandRequest{
DeviceResourceName: "test-resource",
Attributes: nil,
Type: "String",
}
cv := &sdkModels.CommandValue{
DeviceResourceName: "test-resource",
Type: "String",
Value: "test-value",
}
driverMock.On("HandleReadCommands", "test-device", testProtocols, []sdkModels.CommandRequest{cr}).Return(nil, nil)
driverMock.On("HandleWriteCommands", "test-device", testProtocols, []sdkModels.CommandRequest{cr}, []*sdkModels.CommandValue{cv}).Return(nil)

devices := responses.MultiDevicesResponse{
BaseResponse: common.BaseResponse{},
Devices: []dtos.Device{
dtos.FromDeviceModelToDTO(testDevice),
},
}
dcMock := &clientMocks.DeviceClient{}
dcMock.On("DevicesByServiceName", context.Background(), "test-service", 0, -1).Return(devices, nil)

profile := responses.DeviceProfileResponse{
BaseResponse: common.BaseResponse{},
Profile: dtos.DeviceProfile{
Name: "test-profile",
DeviceResources: []dtos.DeviceResource{
dtos.DeviceResource{
Name: "test-resource",
Properties: dtos.ResourceProperties{
ValueType: "String",
ReadWrite: "RW",
DefaultValue: "test-value",
},
},
dtos.DeviceResource{
Name: "ro-resource",
Properties: dtos.ResourceProperties{
ValueType: "String",
ReadWrite: "R",
},
},
dtos.DeviceResource{
Name: "wo-resource",
Properties: dtos.ResourceProperties{
ValueType: "String",
ReadWrite: "W",
},
},
},
DeviceCommands: []dtos.DeviceCommand{
dtos.DeviceCommand{
Name: "test-command",
IsHidden: false,
ReadWrite: "RW",
ResourceOperations: []dtos.ResourceOperation{{DeviceResource: "test-resource"}},
},
dtos.DeviceCommand{
Name: "ro-command",
IsHidden: false,
ReadWrite: "R",
ResourceOperations: []dtos.ResourceOperation{{DeviceResource: "ro-resource"}},
},
dtos.DeviceCommand{
Name: "wo-command",
IsHidden: false,
ReadWrite: "W",
ResourceOperations: []dtos.ResourceOperation{{DeviceResource: "wo-resource"}},
},
dtos.DeviceCommand{
Name: "exceed-command",
IsHidden: false,
ReadWrite: "R",
ResourceOperations: []dtos.ResourceOperation{{DeviceResource: "test-resource"}, {DeviceResource: "ro-resource"}},
},
},
},
}
dpcMock := &clientMocks.DeviceProfileClient{}
dpcMock.On("DeviceProfileByName", context.Background(), "test-profile").Return(profile, nil)

pwcMock := &clientMocks.ProvisionWatcherClient{}
pwcMock.On("ProvisionWatchersByServiceName", context.Background(), "test-service", 0, -1).Return(responses.MultiProvisionWatchersResponse{}, nil)

configuration := &config.ConfigurationStruct{
Device: config.DeviceInfo{MaxCmdOps: 1},
}

dic := di.NewContainer(di.ServiceConstructorMap{
bootstrapContainer.LoggingClientInterfaceName: func(get di.Get) interface{} {
return logger.NewMockClient()
},
container.ProtocolDriverName: func(get di.Get) interface{} {
return driverMock
},
container.MetadataDeviceClientName: func(get di.Get) interface{} {
return dcMock
},
container.MetadataDeviceProfileClientName: func(get di.Get) interface{} {
return dpcMock
},
container.MetadataProvisionWatcherClientName: func(get di.Get) interface{} {
return pwcMock
},
container.ConfigurationName: func(get di.Get) interface{} {
return configuration
},
})

return dic
}

func TestCommandProcessor_ReadDeviceResource(t *testing.T) {
dic := mockDic()
err := cache.InitCache("test-service", dic)
require.NoError(t, err)

valid := NewCommandProcessor(testDevice, "test-resource", uuid.NewString(), "", "", dic)
invalidDeviceResource := NewCommandProcessor(testDevice, "invalid", uuid.NewString(), "", "", dic)
writeOnlyDeviceResource := NewCommandProcessor(testDevice, "wo-resource", uuid.NewString(), "", "", dic)

tests := []struct {
name string
commandProcessor *CommandProcessor
expectedErr bool
}{
{"valid", valid, false},
{"invalid - DeviceResource name not found", invalidDeviceResource, true},
{"invalid - reading write-only DeviceResource", writeOnlyDeviceResource, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.commandProcessor.ReadDeviceResource()
if tt.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}

func TestCommandProcessor_ReadDeviceCommand(t *testing.T) {
dic := mockDic()
err := cache.InitCache("test-service", dic)
require.NoError(t, err)

valid := NewCommandProcessor(testDevice, "test-command", uuid.NewString(), "", "", dic)
invalidDeviceCommand := NewCommandProcessor(testDevice, "invalid", uuid.NewString(), "", "", dic)
writeOnlyDeviceCommand := NewCommandProcessor(testDevice, "wo-command", uuid.NewString(), "", "", dic)
outOfRangeResourceOperation := NewCommandProcessor(testDevice, "exceed-command", uuid.NewString(), "", "", dic)

tests := []struct {
name string
commandProcessor *CommandProcessor
expectedErr bool
}{
{"valid", valid, false},
{"invalid - DeviceCommand name not found", invalidDeviceCommand, true},
{"invalid - reading write-only DeviceCommand", writeOnlyDeviceCommand, true},
{"invalid - RO exceed MaxCmdOps count", outOfRangeResourceOperation, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.commandProcessor.ReadDeviceCommand()
if tt.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}

func TestCommandProcessor_WriteDeviceResource(t *testing.T) {
dic := mockDic()
err := cache.InitCache("test-service", dic)
require.NoError(t, err)

valid := NewCommandProcessor(testDevice, "test-resource", uuid.NewString(), "{\"test-resource\":\"test-value\"}", "", dic)
invalidDeviceResource := NewCommandProcessor(testDevice, "invalid", uuid.NewString(), "", "", dic)
readOnlyDeviceResource := NewCommandProcessor(testDevice, "ro-resource", uuid.NewString(), "", "", dic)
noRequestBody := NewCommandProcessor(testDevice, "test-resource", uuid.NewString(), "", "", dic)
invalidRequestBody := NewCommandProcessor(testDevice, "test-resource", uuid.NewString(), "{\"wrong-resource\":\"wrong-value\"}", "", dic)

tests := []struct {
name string
commandProcessor *CommandProcessor
expectedErr bool
}{
{"valid", valid, false},
{"invalid - DeviceResource name not found", invalidDeviceResource, true},
{"invalid - writing read-only DeviceResource", readOnlyDeviceResource, true},
{"invalid - no set parameter(body) specified", noRequestBody, true},
{"valid - parameter(body) doesn't match requested command, using DefaultValue in DeviceResource.Properties", invalidRequestBody, false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err = tt.commandProcessor.WriteDeviceResource()
if tt.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}

func TestCommandProcessor_WriteDeviceCommand(t *testing.T) {
dic := mockDic()
err := cache.InitCache("test-service", dic)
require.NoError(t, err)

valid := NewCommandProcessor(testDevice, "test-command", uuid.NewString(), "{\"test-resource\":\"test-value\"}", "", dic)
invalidDeviceCommand := NewCommandProcessor(testDevice, "invalid", uuid.NewString(), "", "", dic)
readOnlyDeviceCommand := NewCommandProcessor(testDevice, "ro-command", uuid.NewString(), "", "", dic)
outOfRangeResourceOperation := NewCommandProcessor(testDevice, "exceed-command", uuid.NewString(), "", "", dic)
noRequestBody := NewCommandProcessor(testDevice, "test-command", uuid.NewString(), "", "", dic)
invalidRequestBody := NewCommandProcessor(testDevice, "test-command", uuid.NewString(), "{\"wrong-resource\":\"wrong-value\"}", "", dic)

tests := []struct {
name string
commandProcessor *CommandProcessor
expectedErr bool
}{
{"valid", valid, false},
{"invalid - DeviceCommand name not found", invalidDeviceCommand, true},
{"invalid - writing read-only DeviceCommand", readOnlyDeviceCommand, true},
{"invalid - RO exceed MaxCmdOps count", outOfRangeResourceOperation, true},
{"invalid - no set parameter(body) specified", noRequestBody, true},
{"valid - parameter(body) doesn't match requested command, using DefaultValue in DeviceResource.Properties", invalidRequestBody, false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err = tt.commandProcessor.WriteDeviceCommand()
if tt.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
4 changes: 2 additions & 2 deletions internal/cache/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
"github.com/edgexfoundry/device-sdk-go/v2/internal/container"
)

// Init basic state for cache
func InitV2Cache(name string, dic *di.Container) errors.EdgeX {
// InitCache Init basic state for cache
func InitCache(name string, dic *di.Container) errors.EdgeX {
dc := container.MetadataDeviceClientFrom(dic.Get)
dpc := container.MetadataDeviceProfileClientFrom(dic.Get)
pwc := container.MetadataProvisionWatcherClientFrom(dic.Get)
Expand Down
37 changes: 37 additions & 0 deletions internal/transformer/transformresult_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,40 @@ func Test_commandValueForTransform(t *testing.T) {
})
}
}

func Test_mapCommandValue(t *testing.T) {
numericValue, err := models.NewCommandValue("test-resource", v2.ValueTypeFloat32, float32(123.456))
require.NoError(t, err)
stringValue, err := models.NewCommandValue("test-resource", v2.ValueTypeString, "key")
require.NoError(t, err)
arrayValue, err := models.NewCommandValue("test-resource", v2.ValueTypeInt8Array, []int8{1, 2, 3})
require.NoError(t, err)
invalid, err := models.NewCommandValue("test-resource", v2.ValueTypeString, "invalid")
require.NoError(t, err)

mappings := map[string]string{
"123.456": "value",
"key": "value",
"[1 2 3]": "value",
}

tests := []struct {
name string
cv *models.CommandValue
success bool
}{
{"valid - CommandValue with numeric mapping", numericValue, true},
{"valid - CommandValue with string mapping", stringValue, true},
{"valid - CommandValue with array mapping", arrayValue, true},
{"invalid - mapping not found", invalid, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, ok := mapCommandValue(tt.cv, mappings)
require.Equal(t, ok, tt.success)
if ok {
assert.Equal(t, res.Value, "value")
}
})
}
}
Loading

0 comments on commit c59eaec

Please sign in to comment.