Skip to content

Commit

Permalink
Merge pull request #4817 from weichou1229/issue-4814
Browse files Browse the repository at this point in the history
feat: Implement Get All DeviceProfile BasicInfo API
  • Loading branch information
cloudxxx8 committed Jul 5, 2024
2 parents d3e93d2 + 9494c74 commit cd4dcca
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 6 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/edgexfoundry/go-mod-bootstrap/v3 v3.2.0-dev.38
github.com/edgexfoundry/go-mod-configuration/v3 v3.2.0-dev.7
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.26
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.27
github.com/edgexfoundry/go-mod-messaging/v3 v3.2.0-dev.26
github.com/edgexfoundry/go-mod-secrets/v3 v3.2.0-dev.7
github.com/fxamacker/cbor/v2 v2.7.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ github.com/edgexfoundry/go-mod-bootstrap/v3 v3.2.0-dev.38 h1:I6WfSLF5Xk4S/mQxrN5
github.com/edgexfoundry/go-mod-bootstrap/v3 v3.2.0-dev.38/go.mod h1:TVhjFOP3UsYhtAOgOnORCKO6K0G6AyMpVNO0aMrtCvk=
github.com/edgexfoundry/go-mod-configuration/v3 v3.2.0-dev.7 h1:FTps28H9Phy/oVKJjAOdNFGVXRKXIqQo2rLQV8y5DVA=
github.com/edgexfoundry/go-mod-configuration/v3 v3.2.0-dev.7/go.mod h1:WX+Cqr/+nXRKxNIcvekHdf5ubbTZX0D76Rj2T0FKmtA=
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.26 h1:7b7jMJcF/EEV8yf203q8WjrM5VMPY/DLxWwrKWnasQk=
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.26/go.mod h1:DXNOFlESZek+NiNTSAsXTAOj/DEhpe6jMIbQ5RpnElo=
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.27 h1:kocPmpfUAAhCf2cii0NVkB4gMslYRPEZYJfCLhsx5fs=
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.27/go.mod h1:DXNOFlESZek+NiNTSAsXTAOj/DEhpe6jMIbQ5RpnElo=
github.com/edgexfoundry/go-mod-messaging/v3 v3.2.0-dev.26 h1:Fkiki07fSxofusT6vV510CdoSwE0vy91xJTdPs8bO0Q=
github.com/edgexfoundry/go-mod-messaging/v3 v3.2.0-dev.26/go.mod h1:PXE87Ia/lH5vVQxLMEpzb4e2UTA1e3n8tZXSwRkf0uw=
github.com/edgexfoundry/go-mod-registry/v3 v3.2.0-dev.9 h1:pGSmX00BRUDAWuEnntqtt9E8e7Gbw5iyGG9Fdr7FK0s=
Expand Down
19 changes: 18 additions & 1 deletion internal/core/metadata/application/deviceprofile.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2020-2022 IOTech Ltd
// Copyright (C) 2020-2024 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -226,6 +226,23 @@ func PatchDeviceProfileBasicInfo(ctx context.Context, dto dtos.UpdateDeviceProfi
return nil
}

// AllDeviceProfileBasicInfos query the device profile basic infos with offset, and limit
func AllDeviceProfileBasicInfos(offset int, limit int, labels []string, dic *di.Container) (deviceProfileBasicInfos []dtos.DeviceProfileBasicInfo, totalCount uint32, err errors.EdgeX) {
dbClient := container.DBClientFrom(dic.Get)
dps, err := dbClient.AllDeviceProfiles(offset, limit, labels)
if err == nil {
totalCount, err = dbClient.DeviceProfileCountByLabels(labels)
}
if err != nil {
return deviceProfileBasicInfos, totalCount, errors.NewCommonEdgeXWrapper(err)
}
deviceProfileBasicInfos = make([]dtos.DeviceProfileBasicInfo, len(dps))
for i, dp := range dps {
deviceProfileBasicInfos[i] = dtos.FromDeviceProfileModelToBasicInfoDTO(dp)
}
return deviceProfileBasicInfos, totalCount, nil
}

func deviceProfileByDTO(dbClient interfaces.DBClient, dto dtos.UpdateDeviceProfileBasicInfo) (deviceProfile models.DeviceProfile, err errors.EdgeX) {
// The ID or Name is required by DTO and the DTO also accepts empty string ID if the Name is provided
if dto.Id != nil && *dto.Id != "" {
Expand Down
24 changes: 23 additions & 1 deletion internal/core/metadata/controller/http/deviceprofile.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2021-2023 IOTech Ltd
// Copyright (C) 2021-2024 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -382,3 +382,25 @@ func (dc *DeviceProfileController) PatchDeviceProfileBasicInfo(c echo.Context) e
utils.WriteHttpHeader(w, ctx, http.StatusMultiStatus)
return pkg.EncodeAndWriteResponse(updateResponses, w, lc)
}

func (dc *DeviceProfileController) AllDeviceProfileBasicInfos(c echo.Context) error {
lc := container.LoggingClientFrom(dc.dic.Get)
r := c.Request()
w := c.Response()
ctx := r.Context()
config := metadataContainer.ConfigurationFrom(dc.dic.Get)

// parse URL query string for offset, limit, and labels
offset, limit, labels, err := utils.ParseGetAllObjectsRequestQueryString(c, 0, math.MaxInt32, -1, config.Service.MaxResultCount)
if err != nil {
return utils.WriteErrorResponse(w, ctx, lc, err, "")
}
deviceProfileBasicInfos, totalCount, err := application.AllDeviceProfileBasicInfos(offset, limit, labels, dc.dic)
if err != nil {
return utils.WriteErrorResponse(w, ctx, lc, err, "")
}

response := responseDTO.NewMultiDeviceProfileBasicInfosResponse("", "", http.StatusOK, totalCount, deviceProfileBasicInfos)
utils.WriteHttpHeader(w, ctx, http.StatusOK)
return pkg.EncodeAndWriteResponse(response, w, lc)
}
79 changes: 79 additions & 0 deletions internal/core/metadata/controller/http/deviceprofile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1521,3 +1521,82 @@ func TestDeviceProfilesByManufacturerAndModel(t *testing.T) {
})
}
}

func TestAllDeviceProfileBasicInfos(t *testing.T) {
deviceProfile := dtos.ToDeviceProfileModel(buildTestDeviceProfileRequest().Profile)
deviceProfiles := []models.DeviceProfile{deviceProfile, deviceProfile, deviceProfile}
expectedTotalProfileCount := uint32(3)

dic := mockDic()
dbClientMock := &mocks.DBClient{}
dbClientMock.On("DeviceProfileCountByLabels", []string(nil)).Return(expectedTotalProfileCount, nil)
dbClientMock.On("DeviceProfileCountByLabels", testDeviceProfileLabels).Return(expectedTotalProfileCount, nil)
dbClientMock.On("AllDeviceProfiles", 0, 10, []string(nil)).Return(deviceProfiles, nil)
dbClientMock.On("AllDeviceProfiles", 0, 5, testDeviceProfileLabels).Return([]models.DeviceProfile{deviceProfiles[0], deviceProfiles[1]}, nil)
dbClientMock.On("AllDeviceProfiles", 1, 2, []string(nil)).Return([]models.DeviceProfile{deviceProfiles[1], deviceProfiles[2]}, nil)
dbClientMock.On("AllDeviceProfiles", 4, 1, testDeviceProfileLabels).Return([]models.DeviceProfile{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil))
dic.Update(di.ServiceConstructorMap{
container.DBClientInterfaceName: func(get di.Get) interface{} {
return dbClientMock
},
})
controller := NewDeviceProfileController(dic)
assert.NotNil(t, controller)

tests := []struct {
name string
offset string
limit string
labels string
errorExpected bool
expectedCount int
expectedTotalCount uint32
expectedStatusCode int
}{
{"Valid - get device profile basic infos without labels", "0", "10", "", false, 3, expectedTotalProfileCount, http.StatusOK},
{"Valid - get device profile basic infos with labels", "0", "5", strings.Join(testDeviceProfileLabels, ","), false, 2, expectedTotalProfileCount, http.StatusOK},
{"Valid - get device profile basic infos with offset and no labels", "1", "2", "", false, 2, expectedTotalProfileCount, http.StatusOK},
{"Invalid - offset out of range", "4", "1", strings.Join(testDeviceProfileLabels, ","), true, 0, expectedTotalProfileCount, http.StatusNotFound},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
e := echo.New()
req, err := http.NewRequest(http.MethodGet, common.ApiAllDeviceProfileRoute, http.NoBody)
query := req.URL.Query()
query.Add(common.Offset, testCase.offset)
query.Add(common.Limit, testCase.limit)
if len(testCase.labels) > 0 {
query.Add(common.Labels, testCase.labels)
}
req.URL.RawQuery = query.Encode()
require.NoError(t, err)

// Act
recorder := httptest.NewRecorder()
c := e.NewContext(req, recorder)
err = controller.AllDeviceProfiles(c)
require.NoError(t, err)

// Assert
if testCase.errorExpected {
var res commonDTO.BaseResponse
err = json.Unmarshal(recorder.Body.Bytes(), &res)
require.NoError(t, err)
assert.Equal(t, common.ApiVersion, res.ApiVersion, "API Version not as expected")
assert.Equal(t, testCase.expectedStatusCode, recorder.Result().StatusCode, "HTTP status code not as expected")
assert.Equal(t, testCase.expectedStatusCode, int(res.StatusCode), "Response status code not as expected")
assert.NotEmpty(t, res.Message, "Response message doesn't contain the error message")
} else {
var res responseDTO.MultiDeviceProfileBasicInfoResponse
err = json.Unmarshal(recorder.Body.Bytes(), &res)
require.NoError(t, err)
assert.Equal(t, common.ApiVersion, res.ApiVersion, "API Version not as expected")
assert.Equal(t, testCase.expectedStatusCode, recorder.Result().StatusCode, "HTTP status code not as expected")
assert.Equal(t, testCase.expectedStatusCode, int(res.StatusCode), "Response status code not as expected")
assert.Equal(t, testCase.expectedCount, len(res.Profiles), "Profile count not as expected")
assert.Equal(t, testCase.expectedTotalCount, res.TotalCount, "Total count not as expected")
assert.Empty(t, res.Message, "Message should be empty when it is successful")
}
})
}
}
3 changes: 2 additions & 1 deletion internal/core/metadata/router.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2021-2023 IOTech Ltd
// Copyright (C) 2021-2024 IOTech Ltd
// Copyright (C) 2023 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
Expand Down Expand Up @@ -45,6 +45,7 @@ func LoadRestRoutes(r *echo.Echo, dic *di.Container, serviceName string) {
r.GET(common.ApiDeviceProfileByManufacturerEchoRoute, dc.DeviceProfilesByManufacturer, authenticationHook)
r.GET(common.ApiDeviceProfileByManufacturerAndModelEchoRoute, dc.DeviceProfilesByManufacturerAndModel, authenticationHook)
r.PATCH(common.ApiDeviceProfileBasicInfoRoute, dc.PatchDeviceProfileBasicInfo, authenticationHook)
r.GET(common.ApiAllDeviceProfileBasicInfoRoute, dc.AllDeviceProfileBasicInfos, authenticationHook)

// Device Resource
dr := metadataController.NewDeviceResourceController(dic)
Expand Down
75 changes: 75 additions & 0 deletions openapi/v3/core-metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,15 @@ components:
$ref: '#/components/schemas/DeviceProfileBasicInfo'
required:
- profileName
MultiDeviceProfileBasicInfosResponse:
allOf:
- $ref: '#/components/schemas/BaseWithTotalCountResponse'
type: object
properties:
profiles:
type: array
items:
$ref: '#/components/schemas/DeviceProfileBasicInfo'
DeviceProfile:
description: "A profile defining a class of device to be onboarded, including its capabilities and data format."
type: object
Expand Down Expand Up @@ -1551,6 +1560,27 @@ components:
resourceOperations:
- deviceResource: "1"
defaultValue: "false"
GetAllDeviceProfileBasicInfosResponse:
value:
apiVersion: "v3"
requestId: "bc979763-afde-492c-b0a2-79ff3025b6de"
statusCode: 200
totalCount: 3
profiles:
- id: "9d33b6fd-f38b-4f0e-aef4-0332578ff2c0"
name: "Device-Virtual-Profile"
description: "Example of Device-Virtual"
manufacturer: "IOTech"
model: "Device-Virtual-01"
labels:
- device-virtual-example
- id: "3edf4fe9-b3b8-4f78-bb94-ff55f7d9f316"
name: "Device-Modbus-Profile"
description: "Example of Device-Modbus"
manufacturer: "IOTech"
model: "Device-Modbus-01"
labels:
- device-modbus-example
GetAllProvisionWatchersResponse:
value:
apiVersion: "v3"
Expand Down Expand Up @@ -2579,6 +2609,51 @@ paths:
examples:
500Example:
$ref: '#/components/examples/500Example'
/deviceprofile/basicinfo/all:
parameters:
- $ref: '#/components/parameters/correlatedRequestHeader'
- $ref: '#/components/parameters/offsetParam'
- $ref: '#/components/parameters/limitParam'
- $ref: '#/components/parameters/labelsParam'
get:
summary: "Given the entire range of device profile basic infos sorted by last modified descending, returns a portion of that range according to the offset and limit parameters. Device profiles may also be filtered by label."
responses:
'200':
description: "OK"
headers:
X-Correlation-ID:
$ref: '#/components/headers/correlatedResponseHeader'
content:
application/json:
schema:
$ref: '#/components/schemas/MultiDeviceProfileBasicInfosResponse'
examples:
GetAllDeviceProfilesResponse:
$ref: '#/components/examples/GetAllDeviceProfileBasicInfosResponse'
'400':
description: "Request is in an invalid state"
headers:
X-Correlation-ID:
$ref: '#/components/headers/correlatedResponseHeader'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
400Example:
$ref: '#/components/examples/400Example'
'500':
description: "Internal Server Error"
headers:
X-Correlation-ID:
$ref: '#/components/headers/correlatedResponseHeader'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
500Example:
$ref: '#/components/examples/500Example'
'/deviceprofile/deviceCommand':
parameters:
- $ref: '#/components/parameters/correlatedRequestHeader'
Expand Down

0 comments on commit cd4dcca

Please sign in to comment.