Skip to content

Commit b5baed0

Browse files
committed
feat: Check the auto events before adding or updating the Device
Check the auto events before adding or updating the Device. Close #4767 Signed-off-by: bruce <[email protected]>
1 parent b006ed3 commit b5baed0

File tree

3 files changed

+118
-3
lines changed

3 files changed

+118
-3
lines changed

internal/core/metadata/application/device.go

+37-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (C) 2020-2023 IOTech Ltd
2+
* Copyright (C) 2020-2024 IOTech Ltd
33
* Copyright 2023 Intel Corporation
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
@@ -19,6 +19,7 @@ import (
1919
"context"
2020
goErrors "errors"
2121
"fmt"
22+
"slices"
2223
"time"
2324

2425
bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
@@ -52,7 +53,12 @@ func AddDevice(d models.Device, ctx context.Context, dic *di.Container) (id stri
5253
return id, errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("device service '%s' does not exists", d.ServiceName), nil)
5354
}
5455

55-
err := validateDeviceCallback(dtos.FromDeviceModelToDTO(d), dic)
56+
err := validateAutoEvent(dic, d)
57+
if err != nil {
58+
return "", errors.NewCommonEdgeXWrapper(err)
59+
}
60+
61+
err = validateDeviceCallback(dtos.FromDeviceModelToDTO(d), dic)
5662
if err != nil {
5763
return "", errors.NewCommonEdgeXWrapper(err)
5864
}
@@ -161,6 +167,11 @@ func PatchDevice(dto dtos.UpdateDevice, ctx context.Context, dic *di.Container)
161167

162168
requests.ReplaceDeviceModelFieldsWithDTO(&device, dto)
163169

170+
err = validateAutoEvent(dic, device)
171+
if err != nil {
172+
return errors.NewCommonEdgeXWrapper(err)
173+
}
174+
164175
deviceDTO := dtos.FromDeviceModelToDTO(device)
165176
err = validateDeviceCallback(deviceDTO, dic)
166177
if err != nil {
@@ -262,3 +273,27 @@ func DevicesByProfileName(offset int, limit int, profileName string, dic *di.Con
262273
}
263274

264275
var noMessagingClientError = goErrors.New("MessageBus Client not available. Please update RequireMessageBus and MessageBus configuration to enable sending System Events via the EdgeX MessageBus")
276+
277+
func validateAutoEvent(dic *di.Container, d models.Device) errors.EdgeX {
278+
dbClient := container.DBClientFrom(dic.Get)
279+
dp, err := dbClient.DeviceProfileByName(d.ProfileName)
280+
if err != nil {
281+
return errors.NewCommonEdgeX(errors.Kind(err), fmt.Sprintf("device profile '%s' not found during validating device '%s' auto event", d.ProfileName, d.Name), err)
282+
}
283+
for _, a := range d.AutoEvents {
284+
_, err := time.ParseDuration(a.Interval)
285+
if err != nil {
286+
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("auto event interval '%s' not valid in the device '%s'", a.Interval, d.Name), err)
287+
}
288+
hasResource := slices.ContainsFunc(dp.DeviceResources, func(r models.DeviceResource) bool {
289+
return r.Name == a.SourceName
290+
})
291+
hasCommand := slices.ContainsFunc(dp.DeviceCommands, func(c models.DeviceCommand) bool {
292+
return c.Name == a.SourceName
293+
})
294+
if !hasResource && !hasCommand {
295+
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("auto event source '%s' cannot be found in the device profile '%s'", a.SourceName, dp.Name), nil)
296+
}
297+
}
298+
return nil
299+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// Copyright (C) 2024 IOTech Ltd
3+
//
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
package application
7+
8+
import (
9+
"github.com/edgexfoundry/edgex-go/internal/core/metadata/container"
10+
"testing"
11+
12+
"github.com/edgexfoundry/edgex-go/internal/core/metadata/infrastructure/interfaces/mocks"
13+
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
14+
"github.com/edgexfoundry/go-mod-core-contracts/v3/models"
15+
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/mock"
18+
)
19+
20+
func TestValidateAutoEvents(t *testing.T) {
21+
source1 := "source1"
22+
command1 := "command1"
23+
deviceProfile := models.DeviceProfile{
24+
Name: "test-profile",
25+
DeviceResources: []models.DeviceResource{{Name: source1}, {Name: "resource2"}},
26+
DeviceCommands: []models.DeviceCommand{{Name: command1}, {Name: "command2"}},
27+
}
28+
29+
dic := di.NewContainer(di.ServiceConstructorMap{})
30+
dbClientMock := &mocks.DBClient{}
31+
dbClientMock.On("DeviceProfileByName", mock.Anything).Return(deviceProfile, nil)
32+
dic.Update(di.ServiceConstructorMap{
33+
container.DBClientInterfaceName: func(get di.Get) interface{} {
34+
return dbClientMock
35+
},
36+
})
37+
38+
tests := []struct {
39+
name string
40+
device models.Device
41+
errorExpected bool
42+
}{
43+
{"resource exist",
44+
models.Device{
45+
AutoEvents: []models.AutoEvent{{SourceName: source1, Interval: "1s"}},
46+
},
47+
false,
48+
},
49+
{"command exist",
50+
models.Device{
51+
AutoEvents: []models.AutoEvent{{SourceName: source1, Interval: "1s"}, {SourceName: command1, Interval: "1s"}},
52+
},
53+
false,
54+
},
55+
{"resource not exist",
56+
models.Device{
57+
AutoEvents: []models.AutoEvent{{SourceName: "notFoundSource", Interval: "1s"}},
58+
},
59+
true,
60+
},
61+
{"interval format not valid",
62+
models.Device{
63+
AutoEvents: []models.AutoEvent{{SourceName: source1, Interval: "1"}},
64+
},
65+
true,
66+
},
67+
}
68+
for _, testCase := range tests {
69+
t.Run(testCase.name, func(t *testing.T) {
70+
err := validateAutoEvent(dic, testCase.device)
71+
if testCase.errorExpected {
72+
assert.Error(t, err)
73+
} else {
74+
assert.NoError(t, err)
75+
}
76+
})
77+
}
78+
}

internal/core/metadata/controller/http/device_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (C) 2020-2023 IOTech Ltd
2+
// Copyright (C) 2020-2024 IOTech Ltd
33
//
44
// SPDX-License-Identifier: Apache-2.0
55

@@ -131,6 +131,7 @@ func TestAddDevice(t *testing.T) {
131131
valid := testDevice
132132
dbClientMock.On("DeviceServiceNameExists", deviceModel.ServiceName).Return(true, nil)
133133
dbClientMock.On("AddDevice", deviceModel).Return(deviceModel, nil)
134+
dbClientMock.On("DeviceProfileByName", mock.Anything).Return(models.DeviceProfile{Name: "test-profile", DeviceResources: []models.DeviceResource{{Name: "TestResource"}}}, nil)
134135

135136
notFoundProfile := testDevice
136137
notFoundProfile.Device.ProfileName = "notFoundProfile"
@@ -514,6 +515,7 @@ func TestPatchDevice(t *testing.T) {
514515
dbClientMock.On("DeviceServiceNameExists", *valid.Device.ServiceName).Return(true, nil)
515516
dbClientMock.On("DeviceById", *valid.Device.Id).Return(dsModels, nil)
516517
dbClientMock.On("UpdateDevice", dsModels).Return(nil)
518+
dbClientMock.On("DeviceProfileByName", mock.Anything).Return(models.DeviceProfile{Name: "test-profile", DeviceResources: []models.DeviceResource{{Name: "TestResource"}}}, nil)
517519

518520
validWithNoReqID := testReq
519521
validWithNoReqID.RequestId = ""

0 commit comments

Comments
 (0)