From b7d163c49bb6e4291a16d32f0475986ccc100d11 Mon Sep 17 00:00:00 2001 From: Ginny Guan Date: Fri, 18 Oct 2024 15:57:14 +0800 Subject: [PATCH] fix: check offset, limit, totalCount before executing query close #4948 Signed-off-by: Ginny Guan --- internal/core/data/application/event.go | 39 +++-- internal/core/data/application/reading.go | 140 +++++++++------- .../core/data/controller/http/event_test.go | 8 +- internal/core/metadata/application/device.go | 39 +++-- .../metadata/application/deviceprofile.go | 53 +++++-- .../metadata/application/deviceservice.go | 18 ++- .../metadata/application/provisionwatcher.go | 40 +++-- .../metadata/controller/http/device_test.go | 16 +- .../controller/http/deviceprofile_test.go | 20 +-- .../controller/http/deviceservice_test.go | 4 +- .../controller/http/provisionwatcher_test.go | 17 +- .../infrastructure/postgres/device_profile.go | 16 +- .../pkg/infrastructure/postgres/reading.go | 6 + internal/pkg/utils/count.go | 24 +++ internal/pkg/utils/count_test.go | 40 +++++ .../application/scheduleactionrecord.go | 71 +++++++-- .../cronscheduler/application/schedulejob.go | 14 +- .../http/scheduleactionrecord_test.go | 62 +++++++- .../notifications/application/notification.go | 68 ++++++-- .../notifications/application/subscription.go | 55 +++++-- .../notifications/application/transmission.go | 55 +++++-- .../controller/http/subscription_test.go | 6 +- openapi/v3/core-metadata.yaml | 149 ++++++++++++++++++ openapi/v3/support-cron-scheduler.yaml | 65 ++++++++ openapi/v3/support-scheduler.yaml | 29 ++++ 25 files changed, 848 insertions(+), 206 deletions(-) create mode 100644 internal/pkg/utils/count.go create mode 100644 internal/pkg/utils/count_test.go diff --git a/internal/core/data/application/event.go b/internal/core/data/application/event.go index a819b3c59a..fe3af72358 100644 --- a/internal/core/data/application/event.go +++ b/internal/core/data/application/event.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2020 IOTech Ltd +// Copyright (C) 2020-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -21,6 +21,7 @@ import ( "github.com/edgexfoundry/edgex-go/internal/core/data/container" "github.com/edgexfoundry/edgex-go/internal/pkg/correlation" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" "github.com/google/uuid" ) @@ -188,10 +189,16 @@ func (a *CoreDataApp) DeleteEventsByDeviceName(deviceName string, dic *di.Contai // AllEvents query events by offset and limit func (a *CoreDataApp) AllEvents(offset int, limit int, dic *di.Container) (events []dtos.Event, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - eventModels, err := dbClient.AllEvents(offset, limit) - if err == nil { - totalCount, err = dbClient.EventTotalCount() + totalCount, err = dbClient.EventTotalCount() + if err != nil { + return events, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Event{}, totalCount, err + } + + eventModels, err := dbClient.AllEvents(offset, limit) if err != nil { return events, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -208,10 +215,16 @@ func (a *CoreDataApp) EventsByDeviceName(offset int, limit int, name string, dic return events, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "name is empty", nil) } dbClient := container.DBClientFrom(dic.Get) - eventModels, err := dbClient.EventsByDeviceName(offset, limit, name) - if err == nil { - totalCount, err = dbClient.EventCountByDeviceName(name) + totalCount, err = dbClient.EventCountByDeviceName(name) + if err != nil { + return events, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Event{}, totalCount, err + } + + eventModels, err := dbClient.EventsByDeviceName(offset, limit, name) if err != nil { return events, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -225,10 +238,16 @@ func (a *CoreDataApp) EventsByDeviceName(offset int, limit int, name string, dic // EventsByTimeRange query events with offset, limit and time range func (a *CoreDataApp) EventsByTimeRange(startTime int64, endTime int64, offset int, limit int, dic *di.Container) (events []dtos.Event, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - eventModels, err := dbClient.EventsByTimeRange(startTime, endTime, offset, limit) - if err == nil { - totalCount, err = dbClient.EventCountByTimeRange(startTime, endTime) + totalCount, err = dbClient.EventCountByTimeRange(startTime, endTime) + if err != nil { + return events, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Event{}, totalCount, err } + + eventModels, err := dbClient.EventsByTimeRange(startTime, endTime, offset, limit) if err != nil { return events, totalCount, errors.NewCommonEdgeXWrapper(err) } diff --git a/internal/core/data/application/reading.go b/internal/core/data/application/reading.go index 612b418aaf..6e179cdfad 100644 --- a/internal/core/data/application/reading.go +++ b/internal/core/data/application/reading.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2021-2023 IOTech Ltd +// Copyright (C) 2021-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -18,6 +18,7 @@ import ( "github.com/edgexfoundry/go-mod-core-contracts/v3/models" "github.com/edgexfoundry/edgex-go/internal/core/data/container" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" ) var asyncPurgeReadingOnce sync.Once @@ -37,18 +38,21 @@ func ReadingTotalCount(dic *di.Container) (uint32, errors.EdgeX) { // AllReadings query events by offset, and limit func AllReadings(offset int, limit int, dic *di.Container) (readings []dtos.BaseReading, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - readingModels, err := dbClient.AllReadings(offset, limit) - if err == nil { - readings, err = convertReadingModelsToDTOs(readingModels) - if err == nil { - totalCount, err = dbClient.ReadingTotalCount() - } + totalCount, err = dbClient.ReadingTotalCount() + if err != nil { + return readings, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.BaseReading{}, totalCount, err } + readingModels, err := dbClient.AllReadings(offset, limit) if err != nil { return readings, totalCount, errors.NewCommonEdgeXWrapper(err) } - return readings, totalCount, nil + readings, err = convertReadingModelsToDTOs(readingModels) + return readings, totalCount, err } // ReadingsByResourceName query readings with offset, limit, and resource name @@ -57,18 +61,22 @@ func ReadingsByResourceName(offset int, limit int, resourceName string, dic *di. return readings, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "resourceName is empty", nil) } dbClient := container.DBClientFrom(dic.Get) - readingModels, err := dbClient.ReadingsByResourceName(offset, limit, resourceName) - if err == nil { - readings, err = convertReadingModelsToDTOs(readingModels) - if err == nil { - totalCount, err = dbClient.ReadingCountByResourceName(resourceName) - } + + totalCount, err = dbClient.ReadingCountByResourceName(resourceName) + if err != nil { + return readings, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.BaseReading{}, totalCount, err } + readingModels, err := dbClient.ReadingsByResourceName(offset, limit, resourceName) if err != nil { return readings, totalCount, errors.NewCommonEdgeXWrapper(err) } - return readings, totalCount, nil + readings, err = convertReadingModelsToDTOs(readingModels) + return readings, totalCount, err } // ReadingsByDeviceName query readings with offset, limit, and device name @@ -77,35 +85,42 @@ func ReadingsByDeviceName(offset int, limit int, name string, dic *di.Container) return readings, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "name is empty", nil) } dbClient := container.DBClientFrom(dic.Get) - readingModels, err := dbClient.ReadingsByDeviceName(offset, limit, name) - if err == nil { - readings, err = convertReadingModelsToDTOs(readingModels) - if err == nil { - totalCount, err = dbClient.ReadingCountByDeviceName(name) - } + + totalCount, err = dbClient.ReadingCountByDeviceName(name) + if err != nil { + return readings, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.BaseReading{}, totalCount, err } + readingModels, err := dbClient.ReadingsByDeviceName(offset, limit, name) if err != nil { return readings, totalCount, errors.NewCommonEdgeXWrapper(err) } - return readings, totalCount, nil + readings, err = convertReadingModelsToDTOs(readingModels) + return readings, totalCount, err } // ReadingsByTimeRange query readings with offset, limit and time range func ReadingsByTimeRange(start int64, end int64, offset int, limit int, dic *di.Container) (readings []dtos.BaseReading, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - readingModels, err := dbClient.ReadingsByTimeRange(start, end, offset, limit) - if err == nil { - readings, err = convertReadingModelsToDTOs(readingModels) - if err == nil { - totalCount, err = dbClient.ReadingCountByTimeRange(start, end) - } + totalCount, err = dbClient.ReadingCountByTimeRange(start, end) + if err != nil { + return readings, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.BaseReading{}, totalCount, err } + readingModels, err := dbClient.ReadingsByTimeRange(start, end, offset, limit) if err != nil { return readings, totalCount, errors.NewCommonEdgeXWrapper(err) } - return readings, totalCount, nil + readings, err = convertReadingModelsToDTOs(readingModels) + return readings, totalCount, err } func convertReadingModelsToDTOs(readingModels []models.Reading) (readings []dtos.BaseReading, err errors.EdgeX) { @@ -136,18 +151,21 @@ func ReadingsByResourceNameAndTimeRange(resourceName string, start int64, end in return readings, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "resourceName is empty", nil) } dbClient := container.DBClientFrom(dic.Get) - readingModels, err := dbClient.ReadingsByResourceNameAndTimeRange(resourceName, start, end, offset, limit) - if err == nil { - readings, err = convertReadingModelsToDTOs(readingModels) - if err == nil { - totalCount, err = dbClient.ReadingCountByResourceNameAndTimeRange(resourceName, start, end) - } + totalCount, err = dbClient.ReadingCountByResourceNameAndTimeRange(resourceName, start, end) + if err != nil { + return readings, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.BaseReading{}, totalCount, err } + readingModels, err := dbClient.ReadingsByResourceNameAndTimeRange(resourceName, start, end, offset, limit) if err != nil { return readings, totalCount, errors.NewCommonEdgeXWrapper(err) } - return readings, totalCount, nil + readings, err = convertReadingModelsToDTOs(readingModels) + return readings, totalCount, err } // ReadingsByDeviceNameAndResourceName query readings with offset, limit, device name and its associated resource name @@ -160,18 +178,21 @@ func ReadingsByDeviceNameAndResourceName(deviceName string, resourceName string, } dbClient := container.DBClientFrom(dic.Get) - readingModels, err := dbClient.ReadingsByDeviceNameAndResourceName(deviceName, resourceName, offset, limit) - if err == nil { - readings, err = convertReadingModelsToDTOs(readingModels) - if err == nil { - totalCount, err = dbClient.ReadingCountByDeviceNameAndResourceName(deviceName, resourceName) - } + totalCount, err = dbClient.ReadingCountByDeviceNameAndResourceName(deviceName, resourceName) + if err != nil { + return readings, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.BaseReading{}, totalCount, err } + readingModels, err := dbClient.ReadingsByDeviceNameAndResourceName(deviceName, resourceName, offset, limit) if err != nil { return readings, totalCount, errors.NewCommonEdgeXWrapper(err) } - return readings, totalCount, nil + readings, err = convertReadingModelsToDTOs(readingModels) + return readings, totalCount, err } // ReadingsByDeviceNameAndResourceNameAndTimeRange query readings with offset, limit, device name, its associated resource name and specified time range @@ -184,18 +205,21 @@ func ReadingsByDeviceNameAndResourceNameAndTimeRange(deviceName string, resource } dbClient := container.DBClientFrom(dic.Get) - readingModels, err := dbClient.ReadingsByDeviceNameAndResourceNameAndTimeRange(deviceName, resourceName, start, end, offset, limit) - if err == nil { - readings, err = convertReadingModelsToDTOs(readingModels) - if err == nil { - totalCount, err = dbClient.ReadingCountByDeviceNameAndResourceNameAndTimeRange(deviceName, resourceName, start, end) - } + totalCount, err = dbClient.ReadingCountByDeviceNameAndResourceNameAndTimeRange(deviceName, resourceName, start, end) + if err != nil { + return readings, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.BaseReading{}, totalCount, err } + readingModels, err := dbClient.ReadingsByDeviceNameAndResourceNameAndTimeRange(deviceName, resourceName, start, end, offset, limit) if err != nil { return readings, totalCount, errors.NewCommonEdgeXWrapper(err) } - return readings, totalCount, nil + readings, err = convertReadingModelsToDTOs(readingModels) + return readings, totalCount, err } // ReadingsByDeviceNameAndResourceNamesAndTimeRange query readings with offset, limit, device name, its associated resource name and specified time range @@ -209,19 +233,21 @@ func ReadingsByDeviceNameAndResourceNamesAndTimeRange(deviceName string, resourc if len(resourceNames) > 0 { readingModels, totalCount, err = dbClient.ReadingsByDeviceNameAndResourceNamesAndTimeRange(deviceName, resourceNames, start, end, offset, limit) } else { - readingModels, err = dbClient.ReadingsByDeviceNameAndTimeRange(deviceName, start, end, offset, limit) - if err == nil { - totalCount, err = dbClient.ReadingCountByDeviceNameAndTimeRange(deviceName, start, end) + totalCount, err = dbClient.ReadingCountByDeviceNameAndTimeRange(deviceName, start, end) + if err != nil { + return readings, totalCount, errors.NewCommonEdgeXWrapper(err) } + if cont, err := utils.CheckCountRange(totalCount, offset, limit); !cont { + return []dtos.BaseReading{}, totalCount, err + } + readingModels, err = dbClient.ReadingsByDeviceNameAndTimeRange(deviceName, start, end, offset, limit) } - if err == nil { - readings, err = convertReadingModelsToDTOs(readingModels) - } if err != nil { return readings, totalCount, errors.NewCommonEdgeXWrapper(err) } - return readings, totalCount, nil + readings, err = convertReadingModelsToDTOs(readingModels) + return readings, totalCount, err } // AsyncPurgeReading purge readings and related events according to the retention capability. diff --git a/internal/core/data/controller/http/event_test.go b/internal/core/data/controller/http/event_test.go index a1e1661dc1..736b2e05ae 100644 --- a/internal/core/data/controller/http/event_test.go +++ b/internal/core/data/controller/http/event_test.go @@ -619,7 +619,7 @@ func TestAllEvents(t *testing.T) { dbClientMock.On("EventTotalCount").Return(totalCount, nil) dbClientMock.On("AllEvents", 0, 20).Return(events, nil) dbClientMock.On("AllEvents", 1, 1).Return([]models.Event{events[1]}, nil) - dbClientMock.On("AllEvents", 4, 1).Return([]models.Event{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("AllEvents", 4, 1).Return([]models.Event{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) app := application.NewCoreDataApp(dic) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { @@ -643,7 +643,7 @@ func TestAllEvents(t *testing.T) { }{ {"Valid - get events without offset and limit", "", "", false, 3, totalCount, http.StatusOK}, {"Valid - get events with offset and limit", "1", "1", false, 1, totalCount, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", true, 0, 0, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", true, 0, 0, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { @@ -710,7 +710,7 @@ func TestAllEventsByDeviceName(t *testing.T) { dbClientMock.On("EventsByDeviceName", 0, 5, testDeviceA).Return([]models.Event{events[0], events[1]}, nil) dbClientMock.On("EventsByDeviceName", 0, 5, testDeviceB).Return([]models.Event{events[2]}, nil) dbClientMock.On("EventsByDeviceName", 1, 1, testDeviceA).Return([]models.Event{events[1]}, nil) - dbClientMock.On("EventsByDeviceName", 4, 1, testDeviceB).Return([]models.Event{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("EventsByDeviceName", 4, 1, testDeviceB).Return([]models.Event{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) app := application.NewCoreDataApp(dic) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { @@ -736,7 +736,7 @@ func TestAllEventsByDeviceName(t *testing.T) { {"Valid - get events with deviceName - deviceA", "0", "5", testDeviceA, false, 2, totalCountDeviceA, http.StatusOK}, {"Valid - get events with deviceName - deviceB", "0", "5", testDeviceB, false, 1, totalCountDeviceB, http.StatusOK}, {"Valid - get events with offset and no labels", "1", "1", testDeviceA, false, 1, totalCountDeviceA, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", testDeviceB, true, 0, 0, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", testDeviceB, true, 0, 0, http.StatusRequestedRangeNotSatisfiable}, {"Invalid - get events without deviceName", "0", "10", "", true, 0, 0, http.StatusBadRequest}, } for _, testCase := range tests { diff --git a/internal/core/metadata/application/device.go b/internal/core/metadata/application/device.go index ed13a2bed6..4694de0d68 100644 --- a/internal/core/metadata/application/device.go +++ b/internal/core/metadata/application/device.go @@ -164,10 +164,17 @@ func DevicesByServiceName(offset int, limit int, name string, ctx context.Contex return devices, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "name is empty", nil) } dbClient := container.DBClientFrom(dic.Get) - deviceModels, err := dbClient.DevicesByServiceName(offset, limit, name) - if err == nil { - totalCount, err = dbClient.DeviceCountByServiceName(name) + + totalCount, err = dbClient.DeviceCountByServiceName(name) + if err != nil { + return devices, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Device{}, totalCount, err } + + deviceModels, err := dbClient.DevicesByServiceName(offset, limit, name) if err != nil { return devices, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -290,10 +297,17 @@ func deviceByDTO(dbClient interfaces.DBClient, dto dtos.UpdateDevice) (device mo // AllDevices query the devices with offset, limit, and labels func AllDevices(offset int, limit int, labels []string, dic *di.Container) (devices []dtos.Device, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - deviceModels, err := dbClient.AllDevices(offset, limit, labels) - if err == nil { - totalCount, err = dbClient.DeviceCountByLabels(labels) + + totalCount, err = dbClient.DeviceCountByLabels(labels) + if err != nil { + return devices, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Device{}, totalCount, err + } + + deviceModels, err := dbClient.AllDevices(offset, limit, labels) if err != nil { return devices, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -324,10 +338,17 @@ func DevicesByProfileName(offset int, limit int, profileName string, dic *di.Con return devices, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "profileName is empty", nil) } dbClient := container.DBClientFrom(dic.Get) - deviceModels, err := dbClient.DevicesByProfileName(offset, limit, profileName) - if err == nil { - totalCount, err = dbClient.DeviceCountByProfileName(profileName) + + totalCount, err = dbClient.DeviceCountByProfileName(profileName) + if err != nil { + return devices, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Device{}, totalCount, err } + + deviceModels, err := dbClient.DevicesByProfileName(offset, limit, profileName) if err != nil { return devices, totalCount, errors.NewCommonEdgeXWrapper(err) } diff --git a/internal/core/metadata/application/deviceprofile.go b/internal/core/metadata/application/deviceprofile.go index 061d509736..8126f73bea 100644 --- a/internal/core/metadata/application/deviceprofile.go +++ b/internal/core/metadata/application/deviceprofile.go @@ -12,6 +12,7 @@ import ( "github.com/edgexfoundry/edgex-go/internal/core/metadata/container" "github.com/edgexfoundry/edgex-go/internal/core/metadata/infrastructure/interfaces" "github.com/edgexfoundry/edgex-go/internal/pkg/correlation" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" @@ -142,10 +143,17 @@ func DeleteDeviceProfileByName(name string, ctx context.Context, dic *di.Contain // AllDeviceProfiles query the device profiles with offset, and limit func AllDeviceProfiles(offset int, limit int, labels []string, dic *di.Container) (deviceProfiles []dtos.DeviceProfile, 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) + + totalCount, err = dbClient.DeviceProfileCountByLabels(labels) + if err != nil { + return deviceProfiles, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.DeviceProfile{}, totalCount, err + } + + dps, err := dbClient.AllDeviceProfiles(offset, limit, labels) if err != nil { return deviceProfiles, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -161,11 +169,18 @@ func DeviceProfilesByModel(offset int, limit int, model string, dic *di.Containe if model == "" { return deviceProfiles, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "model is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - dps, err := dbClient.DeviceProfilesByModel(offset, limit, model) - if err == nil { - totalCount, err = dbClient.DeviceProfileCountByModel(model) + totalCount, err = dbClient.DeviceProfileCountByModel(model) + if err != nil { + return deviceProfiles, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.DeviceProfile{}, totalCount, err + } + + dps, err := dbClient.DeviceProfilesByModel(offset, limit, model) if err != nil { return deviceProfiles, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -181,11 +196,18 @@ func DeviceProfilesByManufacturer(offset int, limit int, manufacturer string, di if manufacturer == "" { return deviceProfiles, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "manufacturer is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - dps, err := dbClient.DeviceProfilesByManufacturer(offset, limit, manufacturer) - if err == nil { - totalCount, err = dbClient.DeviceProfileCountByManufacturer(manufacturer) + totalCount, err = dbClient.DeviceProfileCountByManufacturer(manufacturer) + if err != nil { + return deviceProfiles, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.DeviceProfile{}, totalCount, err + } + + dps, err := dbClient.DeviceProfilesByManufacturer(offset, limit, manufacturer) if err != nil { return deviceProfiles, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -245,10 +267,17 @@ func PatchDeviceProfileBasicInfo(ctx context.Context, dto dtos.UpdateDeviceProfi // 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) + + totalCount, err = dbClient.DeviceProfileCountByLabels(labels) + if err != nil { + return deviceProfileBasicInfos, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return deviceProfileBasicInfos, totalCount, err + } + + dps, err := dbClient.AllDeviceProfiles(offset, limit, labels) if err != nil { return deviceProfileBasicInfos, totalCount, errors.NewCommonEdgeXWrapper(err) } diff --git a/internal/core/metadata/application/deviceservice.go b/internal/core/metadata/application/deviceservice.go index 7bd3a48f7e..77e3f8046b 100644 --- a/internal/core/metadata/application/deviceservice.go +++ b/internal/core/metadata/application/deviceservice.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2020-2022 IOTech Ltd +// Copyright (C) 2020-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -8,13 +8,14 @@ package application import ( "context" "fmt" - "github.com/edgexfoundry/go-mod-core-contracts/v3/common" "github.com/edgexfoundry/edgex-go/internal/core/metadata/container" "github.com/edgexfoundry/edgex-go/internal/core/metadata/infrastructure/interfaces" "github.com/edgexfoundry/edgex-go/internal/pkg/correlation" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" + "github.com/edgexfoundry/go-mod-core-contracts/v3/common" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" @@ -140,10 +141,17 @@ func DeleteDeviceServiceByName(name string, ctx context.Context, dic *di.Contain // AllDeviceServices query the device services with labels, offset, and limit func AllDeviceServices(offset int, limit int, labels []string, ctx context.Context, dic *di.Container) (deviceServices []dtos.DeviceService, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - services, err := dbClient.AllDeviceServices(offset, limit, labels) - if err == nil { - totalCount, err = dbClient.DeviceServiceCountByLabels(labels) + + totalCount, err = dbClient.DeviceServiceCountByLabels(labels) + if err != nil { + return deviceServices, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.DeviceService{}, totalCount, err } + + services, err := dbClient.AllDeviceServices(offset, limit, labels) if err != nil { return deviceServices, totalCount, errors.NewCommonEdgeXWrapper(err) } diff --git a/internal/core/metadata/application/provisionwatcher.go b/internal/core/metadata/application/provisionwatcher.go index ccbc696df6..e8f487cdaf 100644 --- a/internal/core/metadata/application/provisionwatcher.go +++ b/internal/core/metadata/application/provisionwatcher.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2021-2023 IOTech Ltd +// Copyright (C) 2021-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -12,6 +12,7 @@ import ( "github.com/edgexfoundry/edgex-go/internal/core/metadata/container" "github.com/edgexfoundry/edgex-go/internal/core/metadata/infrastructure/interfaces" "github.com/edgexfoundry/edgex-go/internal/pkg/correlation" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" @@ -75,10 +76,16 @@ func ProvisionWatchersByServiceName(offset int, limit int, name string, dic *di. } dbClient := container.DBClientFrom(dic.Get) - pwModels, err := dbClient.ProvisionWatchersByServiceName(offset, limit, name) - if err == nil { - totalCount, err = dbClient.ProvisionWatcherCountByServiceName(name) + totalCount, err = dbClient.ProvisionWatcherCountByServiceName(name) + if err != nil { + return provisionWatchers, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.ProvisionWatcher{}, totalCount, err + } + + pwModels, err := dbClient.ProvisionWatchersByServiceName(offset, limit, name) if err != nil { return provisionWatchers, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -96,10 +103,16 @@ func ProvisionWatchersByProfileName(offset int, limit int, name string, dic *di. } dbClient := container.DBClientFrom(dic.Get) - pwModels, err := dbClient.ProvisionWatchersByProfileName(offset, limit, name) - if err == nil { - totalCount, err = dbClient.ProvisionWatcherCountByProfileName(name) + totalCount, err = dbClient.ProvisionWatcherCountByProfileName(name) + if err != nil { + return provisionWatchers, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.ProvisionWatcher{}, totalCount, err } + + pwModels, err := dbClient.ProvisionWatchersByProfileName(offset, limit, name) if err != nil { return provisionWatchers, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -113,10 +126,17 @@ func ProvisionWatchersByProfileName(offset int, limit int, name string, dic *di. // AllProvisionWatchers query the provision watchers with offset, limit and labels func AllProvisionWatchers(offset int, limit int, labels []string, dic *di.Container) (provisionWatchers []dtos.ProvisionWatcher, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - pwModels, err := dbClient.AllProvisionWatchers(offset, limit, labels) - if err == nil { - totalCount, err = dbClient.ProvisionWatcherCountByLabels(labels) + + totalCount, err = dbClient.ProvisionWatcherCountByLabels(labels) + if err != nil { + return provisionWatchers, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.ProvisionWatcher{}, totalCount, err } + + pwModels, err := dbClient.AllProvisionWatchers(offset, limit, labels) if err != nil { return provisionWatchers, totalCount, errors.NewCommonEdgeXWrapper(err) } diff --git a/internal/core/metadata/controller/http/device_test.go b/internal/core/metadata/controller/http/device_test.go index 1575734c91..608b0ad6c8 100644 --- a/internal/core/metadata/controller/http/device_test.go +++ b/internal/core/metadata/controller/http/device_test.go @@ -390,13 +390,15 @@ func TestAllDeviceByServiceName(t *testing.T) { devices := []models.Device{device1WithServiceA, device2WithServiceA, device3WithServiceB} expectedTotalCountServiceA := uint32(2) + expectedTotalCountServiceB := uint32(1) dic := mockDic() dbClientMock := &dbMock.DBClient{} dbClientMock.On("DeviceCountByServiceName", testServiceA).Return(expectedTotalCountServiceA, nil) + dbClientMock.On("DeviceCountByServiceName", testServiceB).Return(expectedTotalCountServiceB, nil) dbClientMock.On("DevicesByServiceName", 0, 5, testServiceA).Return([]models.Device{devices[0], devices[1]}, nil) dbClientMock.On("DevicesByServiceName", 1, 1, testServiceA).Return([]models.Device{devices[1]}, nil) - dbClientMock.On("DevicesByServiceName", 4, 1, testServiceB).Return([]models.Device{}, edgexErr.NewCommonEdgeX(edgexErr.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("DevicesByServiceName", 4, 1, testServiceB).Return([]models.Device{}, edgexErr.NewCommonEdgeX(edgexErr.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -417,7 +419,7 @@ func TestAllDeviceByServiceName(t *testing.T) { }{ {"Valid - get devices with serviceName", "0", "5", testServiceA, false, 2, expectedTotalCountServiceA, http.StatusOK}, {"Valid - get devices with offset and no labels", "1", "1", testServiceA, false, 1, expectedTotalCountServiceA, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", testServiceB, true, 0, 0, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", testServiceB, true, 0, 0, http.StatusRequestedRangeNotSatisfiable}, {"Invalid - get devices without serviceName", "0", "10", "", true, 0, 0, http.StatusBadRequest}, } for _, testCase := range tests { @@ -746,7 +748,7 @@ func TestAllDevices(t *testing.T) { dbClientMock.On("AllDevices", 0, 10, []string(nil)).Return(devices, nil) dbClientMock.On("AllDevices", 0, 5, testDeviceLabels).Return([]models.Device{devices[0], devices[1]}, nil) dbClientMock.On("AllDevices", 1, 2, []string(nil)).Return([]models.Device{devices[1], devices[2]}, nil) - dbClientMock.On("AllDevices", 4, 1, testDeviceLabels).Return([]models.Device{}, edgexErr.NewCommonEdgeX(edgexErr.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("AllDevices", 4, 1, testDeviceLabels).Return([]models.Device{}, edgexErr.NewCommonEdgeX(edgexErr.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -768,7 +770,7 @@ func TestAllDevices(t *testing.T) { {"Valid - get devices without labels", "0", "10", "", false, 3, expectedDeviceTotalCount, http.StatusOK}, {"Valid - get devices with labels", "0", "5", strings.Join(testDeviceLabels, ","), false, 2, expectedDeviceTotalCount, http.StatusOK}, {"Valid - get devices with offset and no labels", "1", "2", "", false, 2, expectedDeviceTotalCount, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", strings.Join(testDeviceLabels, ","), true, 0, expectedDeviceTotalCount, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", strings.Join(testDeviceLabels, ","), true, 0, expectedDeviceTotalCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { @@ -893,13 +895,15 @@ func TestDevicesByProfileName(t *testing.T) { devices := []models.Device{device1WithProfileA, device2WithProfileA, device3WithProfileB} expectedTotalCountProfileA := uint32(2) + expectedTotalCountProfileB := uint32(1) dic := mockDic() dbClientMock := &dbMock.DBClient{} dbClientMock.On("DeviceCountByProfileName", testProfileA).Return(expectedTotalCountProfileA, nil) + dbClientMock.On("DeviceCountByProfileName", testProfileB).Return(expectedTotalCountProfileB, nil) dbClientMock.On("DevicesByProfileName", 0, 5, testProfileA).Return([]models.Device{devices[0], devices[1]}, nil) dbClientMock.On("DevicesByProfileName", 1, 1, testProfileA).Return([]models.Device{devices[1]}, nil) - dbClientMock.On("DevicesByProfileName", 4, 1, testProfileB).Return([]models.Device{}, edgexErr.NewCommonEdgeX(edgexErr.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("DevicesByProfileName", 4, 1, testProfileB).Return([]models.Device{}, edgexErr.NewCommonEdgeX(edgexErr.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -920,7 +924,7 @@ func TestDevicesByProfileName(t *testing.T) { }{ {"Valid - get devices with profileName", "0", "5", testProfileA, false, 2, expectedTotalCountProfileA, http.StatusOK}, {"Valid - get devices with offset and limit", "1", "1", testProfileA, false, 1, expectedTotalCountProfileA, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", testProfileB, true, 0, 0, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", testProfileB, true, 0, 0, http.StatusRequestedRangeNotSatisfiable}, {"Invalid - get devices without profileName", "0", "10", "", true, 0, 0, http.StatusBadRequest}, } for _, testCase := range tests { diff --git a/internal/core/metadata/controller/http/deviceprofile_test.go b/internal/core/metadata/controller/http/deviceprofile_test.go index f1d2fa5e6a..3e2c85fd6e 100644 --- a/internal/core/metadata/controller/http/deviceprofile_test.go +++ b/internal/core/metadata/controller/http/deviceprofile_test.go @@ -1228,7 +1228,7 @@ func TestAllDeviceProfiles(t *testing.T) { 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)) + dbClientMock.On("AllDeviceProfiles", 4, 1, testDeviceProfileLabels).Return([]models.DeviceProfile{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -1250,7 +1250,7 @@ func TestAllDeviceProfiles(t *testing.T) { {"Valid - get device profiles without labels", "0", "10", "", false, 3, expectedTotalProfileCount, http.StatusOK}, {"Valid - get device profiles with labels", "0", "5", strings.Join(testDeviceProfileLabels, ","), false, 2, expectedTotalProfileCount, http.StatusOK}, {"Valid - get device profiles 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}, + {"Invalid - offset out of range", "4", "1", strings.Join(testDeviceProfileLabels, ","), true, 0, expectedTotalProfileCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { @@ -1305,7 +1305,7 @@ func TestDeviceProfilesByModel(t *testing.T) { dbClientMock.On("DeviceProfileCountByModel", TestModel).Return(expectedTotalProfileCount, nil) dbClientMock.On("DeviceProfilesByModel", 0, 10, TestModel).Return(deviceProfiles, nil) dbClientMock.On("DeviceProfilesByModel", 1, 2, TestModel).Return([]models.DeviceProfile{deviceProfiles[1], deviceProfiles[2]}, nil) - dbClientMock.On("DeviceProfilesByModel", 4, 1, TestModel).Return([]models.DeviceProfile{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("DeviceProfilesByModel", 4, 1, TestModel).Return([]models.DeviceProfile{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -1326,7 +1326,7 @@ func TestDeviceProfilesByModel(t *testing.T) { }{ {"Valid - get device profiles by model", "0", "10", TestModel, false, 3, expectedTotalProfileCount, http.StatusOK}, {"Valid - get device profiles by model with offset and limit", "1", "2", TestModel, false, 2, expectedTotalProfileCount, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", TestModel, true, 0, expectedTotalProfileCount, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", TestModel, true, 0, expectedTotalProfileCount, http.StatusRequestedRangeNotSatisfiable}, {"Invalid - model is empty", "0", "10", "", true, 0, expectedTotalProfileCount, http.StatusBadRequest}, } for _, testCase := range tests { @@ -1381,7 +1381,7 @@ func TestDeviceProfilesByManufacturer(t *testing.T) { dbClientMock.On("DeviceProfileCountByManufacturer", TestManufacturer).Return(expectedTotalProfileCount, nil) dbClientMock.On("DeviceProfilesByManufacturer", 0, 10, TestManufacturer).Return(deviceProfiles, nil) dbClientMock.On("DeviceProfilesByManufacturer", 1, 2, TestManufacturer).Return([]models.DeviceProfile{deviceProfiles[1], deviceProfiles[2]}, nil) - dbClientMock.On("DeviceProfilesByManufacturer", 4, 1, TestManufacturer).Return([]models.DeviceProfile{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("DeviceProfilesB yManufacturer", 4, 1, TestManufacturer).Return([]models.DeviceProfile{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -1402,7 +1402,7 @@ func TestDeviceProfilesByManufacturer(t *testing.T) { }{ {"Valid - get device profiles by manufacturer", "0", "10", TestManufacturer, false, 3, expectedTotalProfileCount, http.StatusOK}, {"Valid - get device profiles by manufacturer with offset and limit", "1", "2", TestManufacturer, false, 2, expectedTotalProfileCount, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", TestManufacturer, true, 0, expectedTotalProfileCount, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", TestManufacturer, true, 0, expectedTotalProfileCount, http.StatusRequestedRangeNotSatisfiable}, {"Invalid - manufacturer is empty", "0", "10", "", true, 0, expectedTotalProfileCount, http.StatusBadRequest}, } for _, testCase := range tests { @@ -1456,7 +1456,7 @@ func TestDeviceProfilesByManufacturerAndModel(t *testing.T) { dbClientMock := &mocks.DBClient{} dbClientMock.On("DeviceProfilesByManufacturerAndModel", 0, 10, TestManufacturer, TestModel).Return(deviceProfiles, expectedTotalProfileCount, nil) dbClientMock.On("DeviceProfilesByManufacturerAndModel", 1, 2, TestManufacturer, TestModel).Return([]models.DeviceProfile{deviceProfiles[1], deviceProfiles[2]}, expectedTotalProfileCount, nil) - dbClientMock.On("DeviceProfilesByManufacturerAndModel", 4, 1, TestManufacturer, TestModel).Return([]models.DeviceProfile{}, expectedTotalProfileCount, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("DeviceProfilesByManufacturerAndModel", 4, 1, TestManufacturer, TestModel).Return([]models.DeviceProfile{}, expectedTotalProfileCount, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -1478,7 +1478,7 @@ func TestDeviceProfilesByManufacturerAndModel(t *testing.T) { }{ {"Valid - get device profiles by manufacturer and model", "0", "10", TestManufacturer, TestModel, false, 3, expectedTotalProfileCount, http.StatusOK}, {"Valid - get device profiles by manufacturer with offset and limit", "1", "2", TestManufacturer, TestModel, false, 2, expectedTotalProfileCount, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", TestManufacturer, TestModel, true, 0, expectedTotalProfileCount, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", TestManufacturer, TestModel, true, 0, expectedTotalProfileCount, http.StatusRequestedRangeNotSatisfiable}, {"Invalid - manufacturer is empty", "0", "10", "", TestModel, true, 0, expectedTotalProfileCount, http.StatusBadRequest}, {"Invalid - model is empty", "0", "10", TestManufacturer, "", true, 0, expectedTotalProfileCount, http.StatusBadRequest}, } @@ -1536,7 +1536,7 @@ func TestAllDeviceProfileBasicInfos(t *testing.T) { 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)) + dbClientMock.On("AllDeviceProfiles", 4, 1, testDeviceProfileLabels).Return([]models.DeviceProfile{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -1558,7 +1558,7 @@ func TestAllDeviceProfileBasicInfos(t *testing.T) { {"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}, + {"Invalid - offset out of range", "4", "1", strings.Join(testDeviceProfileLabels, ","), true, 0, expectedTotalProfileCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { diff --git a/internal/core/metadata/controller/http/deviceservice_test.go b/internal/core/metadata/controller/http/deviceservice_test.go index 8188a171cb..c545452d16 100644 --- a/internal/core/metadata/controller/http/deviceservice_test.go +++ b/internal/core/metadata/controller/http/deviceservice_test.go @@ -413,7 +413,7 @@ func TestAllDeviceServices(t *testing.T) { dbClientMock.On("AllDeviceServices", 0, 10, []string(nil)).Return(deviceServices, nil) dbClientMock.On("AllDeviceServices", 0, 5, testDeviceServiceLabels).Return([]models.DeviceService{deviceServices[0], deviceServices[1]}, nil) dbClientMock.On("AllDeviceServices", 1, 2, []string(nil)).Return([]models.DeviceService{deviceServices[1], deviceServices[2]}, nil) - dbClientMock.On("AllDeviceServices", 4, 1, testDeviceServiceLabels).Return([]models.DeviceService{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("AllDeviceServices", 4, 1, testDeviceServiceLabels).Return([]models.DeviceService{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -435,7 +435,7 @@ func TestAllDeviceServices(t *testing.T) { {"Valid - get device services without labels", "0", "10", "", false, 3, expectedTotalDeviceServiceCount, http.StatusOK}, {"Valid - get device services with labels", "0", "5", strings.Join(testDeviceServiceLabels, ","), false, 2, expectedTotalDeviceServiceCount, http.StatusOK}, {"Valid - get device services with offset and no labels", "1", "2", "", false, 2, expectedTotalDeviceServiceCount, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", strings.Join(testDeviceServiceLabels, ","), true, 0, expectedTotalDeviceServiceCount, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", strings.Join(testDeviceServiceLabels, ","), true, 0, expectedTotalDeviceServiceCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { diff --git a/internal/core/metadata/controller/http/provisionwatcher_test.go b/internal/core/metadata/controller/http/provisionwatcher_test.go index 97b6e2f7ce..c60be50324 100644 --- a/internal/core/metadata/controller/http/provisionwatcher_test.go +++ b/internal/core/metadata/controller/http/provisionwatcher_test.go @@ -369,13 +369,15 @@ func TestProvisionWatcherController_ProvisionWatchersByServiceName(t *testing.T) provisionWatchers := []models.ProvisionWatcher{pw1WithServiceA, pw2WithServiceA, pw3WithServiceB} expectedTotalCountServiceA := uint32(2) + expectedTotalCountServiceB := uint32(1) dic := mockDic() dbClientMock := &mocks.DBClient{} dbClientMock.On("ProvisionWatcherCountByServiceName", testServiceA).Return(expectedTotalCountServiceA, nil) + dbClientMock.On("ProvisionWatcherCountByServiceName", testServiceB).Return(expectedTotalCountServiceB, nil) dbClientMock.On("ProvisionWatchersByServiceName", 0, 5, testServiceA).Return([]models.ProvisionWatcher{provisionWatchers[0], provisionWatchers[1]}, nil) dbClientMock.On("ProvisionWatchersByServiceName", 1, 1, testServiceA).Return([]models.ProvisionWatcher{provisionWatchers[1]}, nil) - dbClientMock.On("ProvisionWatchersByServiceName", 4, 1, testServiceB).Return([]models.ProvisionWatcher{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("ProvisionWatchersByServiceName", 4, 1, testServiceB).Return([]models.ProvisionWatcher{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -396,7 +398,7 @@ func TestProvisionWatcherController_ProvisionWatchersByServiceName(t *testing.T) }{ {"Valid - get provision watchers with serviceName", "0", "5", testServiceA, false, 2, expectedTotalCountServiceA, http.StatusOK}, {"Valid - get provision watchers with offset and limit", "1", "1", testServiceA, false, 1, expectedTotalCountServiceA, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", testServiceB, true, 0, expectedTotalCountServiceA, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", testServiceB, true, 0, expectedTotalCountServiceA, http.StatusRequestedRangeNotSatisfiable}, {"Invalid - get provision watchers without serviceName", "0", "10", "", true, 0, expectedTotalCountServiceA, http.StatusBadRequest}, } for _, testCase := range tests { @@ -454,14 +456,15 @@ func TestProvisionWatcherController_ProvisionWatchersByProfileName(t *testing.T) provisionWatchers := []models.ProvisionWatcher{pw1WithProfileA, pw2WithProfileA, pw3WithProfileB} expectedTotalPWCountProfileA := uint32(2) - + expectedTotalPWCountProfileB := uint32(1) dic := mockDic() dbClientMock := &mocks.DBClient{} dbClientMock.On("ProvisionWatcherCountByProfileName", testProfileA).Return(expectedTotalPWCountProfileA, nil) + dbClientMock.On("ProvisionWatcherCountByProfileName", testProfileB).Return(expectedTotalPWCountProfileB, nil) dbClientMock.On("ProvisionWatchersByProfileName", 0, 5, testProfileA).Return([]models.ProvisionWatcher{provisionWatchers[0], provisionWatchers[1]}, nil) dbClientMock.On("ProvisionWatchersByProfileName", 0, 5, testProfileA).Return([]models.ProvisionWatcher{provisionWatchers[0], provisionWatchers[1]}, nil) dbClientMock.On("ProvisionWatchersByProfileName", 1, 1, testProfileA).Return([]models.ProvisionWatcher{provisionWatchers[1]}, nil) - dbClientMock.On("ProvisionWatchersByProfileName", 4, 1, testProfileB).Return([]models.ProvisionWatcher{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("ProvisionWatchersByProfileName", 4, 1, testProfileB).Return([]models.ProvisionWatcher{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -482,7 +485,7 @@ func TestProvisionWatcherController_ProvisionWatchersByProfileName(t *testing.T) }{ {"Valid - get provision watchers with profileName", "0", "5", testProfileA, false, 2, expectedTotalPWCountProfileA, http.StatusOK}, {"Valid - get provision watchers with offset and limit", "1", "1", testProfileA, false, 1, expectedTotalPWCountProfileA, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", testProfileB, true, 0, expectedTotalPWCountProfileA, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", testProfileB, true, 0, expectedTotalPWCountProfileA, http.StatusRequestedRangeNotSatisfiable}, {"Invalid - get provision watchers without profileName", "0", "10", "", true, 0, expectedTotalPWCountProfileA, http.StatusBadRequest}, } for _, testCase := range tests { @@ -539,7 +542,7 @@ func TestProvisionWatcherController_AllProvisionWatchers(t *testing.T) { dbClientMock.On("AllProvisionWatchers", 0, 10, []string(nil)).Return(provisionWatchers, nil) dbClientMock.On("AllProvisionWatchers", 0, 5, testProvisionWatcherLabels).Return([]models.ProvisionWatcher{provisionWatchers[0], provisionWatchers[1]}, nil) dbClientMock.On("AllProvisionWatchers", 1, 2, []string(nil)).Return([]models.ProvisionWatcher{provisionWatchers[1], provisionWatchers[2]}, nil) - dbClientMock.On("AllProvisionWatchers", 4, 1, testProvisionWatcherLabels).Return([]models.ProvisionWatcher{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("AllProvisionWatchers", 4, 1, testProvisionWatcherLabels).Return([]models.ProvisionWatcher{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -561,7 +564,7 @@ func TestProvisionWatcherController_AllProvisionWatchers(t *testing.T) { {"Valid - get provision watchers without labels", "0", "10", "", false, 3, expectedTotalPWCount, http.StatusOK}, {"Valid - get provision watchers with labels", "0", "5", strings.Join(testProvisionWatcherLabels, ","), false, 2, expectedTotalPWCount, http.StatusOK}, {"Valid - get provision watchers with offset and no labels", "1", "2", "", false, 2, expectedTotalPWCount, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", strings.Join(testProvisionWatcherLabels, ","), true, 0, expectedTotalPWCount, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", strings.Join(testProvisionWatcherLabels, ","), true, 0, expectedTotalPWCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { diff --git a/internal/pkg/infrastructure/postgres/device_profile.go b/internal/pkg/infrastructure/postgres/device_profile.go index e76e33043a..68dbc18f17 100644 --- a/internal/pkg/infrastructure/postgres/device_profile.go +++ b/internal/pkg/infrastructure/postgres/device_profile.go @@ -17,6 +17,7 @@ import ( pkgCommon "github.com/edgexfoundry/edgex-go/internal/pkg/common" pgClient "github.com/edgexfoundry/edgex-go/internal/pkg/db/postgres" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" model "github.com/edgexfoundry/go-mod-core-contracts/v3/models" ) @@ -179,12 +180,21 @@ func (c *Client) DeviceProfilesByManufacturer(offset int, limit int, manufacture } // DeviceProfilesByManufacturerAndModel query device profiles with offset, limit, manufacturer and model -func (c *Client) DeviceProfilesByManufacturerAndModel(offset int, limit int, manufacturer string, model string) ([]model.DeviceProfile, uint32, errors.EdgeX) { +func (c *Client) DeviceProfilesByManufacturerAndModel(offset int, limit int, manufacturer string, model string) (profiles []model.DeviceProfile, totalCount uint32, err errors.EdgeX) { ctx := context.Background() offset, validLimit := getValidOffsetAndLimit(offset, limit) queryObj := map[string]any{modelField: model, manufacturerField: manufacturer} - profiles, err := queryDeviceProfiles(ctx, c.ConnPool, sqlQueryContentByJSONFieldWithPagination(deviceProfileTableName), queryObj, offset, validLimit) - return profiles, uint32(len(profiles)), err + totalCount, err = getTotalRowsCount(ctx, c.ConnPool, sqlQueryCountByJSONField(deviceProfileTableName), queryObj) + if err != nil { + return profiles, totalCount, err + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return profiles, totalCount, err + } + profiles, err = queryDeviceProfiles(ctx, c.ConnPool, sqlQueryContentByJSONFieldWithPagination(deviceProfileTableName), queryObj, offset, validLimit) + + return profiles, totalCount, err } // DeviceProfileCountByLabels returns the total count of Device Profiles with labels specified. If no label is specified, the total count of all device profiles will be returned. diff --git a/internal/pkg/infrastructure/postgres/reading.go b/internal/pkg/infrastructure/postgres/reading.go index 38ef036c13..4509ce55c9 100644 --- a/internal/pkg/infrastructure/postgres/reading.go +++ b/internal/pkg/infrastructure/postgres/reading.go @@ -13,6 +13,7 @@ import ( pgClient "github.com/edgexfoundry/edgex-go/internal/pkg/db/postgres" dbModels "github.com/edgexfoundry/edgex-go/internal/pkg/infrastructure/postgres/models" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" model "github.com/edgexfoundry/go-mod-core-contracts/v3/models" @@ -156,6 +157,11 @@ func (c *Client) ReadingsByDeviceNameAndResourceNamesAndTimeRange(deviceName str if err != nil { return nil, 0, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return readings, totalCount, err + } + return readings, totalCount, nil } diff --git a/internal/pkg/utils/count.go b/internal/pkg/utils/count.go new file mode 100644 index 0000000000..f3b8d0cb60 --- /dev/null +++ b/internal/pkg/utils/count.go @@ -0,0 +1,24 @@ +// +// Copyright (C) 2024 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "fmt" + + "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" +) + +// CheckCountRange evaluates if the offset and limit parameters are within the valid range. +func CheckCountRange(totalCount uint32, offset, limit int) (continueExec bool, err errors.EdgeX) { + if limit == 0 || totalCount == 0 { + return false, nil + } + if offset > int(totalCount) { + return false, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, fmt.Sprintf("query objects bounds out of range. length:%v offset:%v", totalCount, offset), nil) + } + + return true, nil +} diff --git a/internal/pkg/utils/count_test.go b/internal/pkg/utils/count_test.go new file mode 100644 index 0000000000..76b5d66c84 --- /dev/null +++ b/internal/pkg/utils/count_test.go @@ -0,0 +1,40 @@ +// +// Copyright (C) 2024 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestCheckCountRange(t *testing.T) { + count := uint32(1) + tests := []struct { + name string + totalCount uint32 + offset int + limit int + continueExec bool + expectErr bool + }{ + {"valid - total count is zero ", uint32(0), 0, 0, false, false}, + {"valid - limit is zero ", count, 0, 0, false, false}, + {"valid - valid range", count, 0, 1, true, false}, + {"invalid - offset out of range", count, 2, 1, false, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cont, err := CheckCountRange(tt.totalCount, tt.offset, tt.limit) + require.Equal(t, tt.continueExec, cont) + if tt.continueExec { + require.NoError(t, err) + } + if tt.expectErr { + require.Error(t, err) + } + }) + } +} diff --git a/internal/support/cronscheduler/application/scheduleactionrecord.go b/internal/support/cronscheduler/application/scheduleactionrecord.go index d24edc5222..045156fc28 100644 --- a/internal/support/cronscheduler/application/scheduleactionrecord.go +++ b/internal/support/cronscheduler/application/scheduleactionrecord.go @@ -22,6 +22,7 @@ import ( "github.com/edgexfoundry/go-mod-core-contracts/v3/models" "github.com/edgexfoundry/edgex-go/internal/pkg/correlation" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" "github.com/edgexfoundry/edgex-go/internal/support/cronscheduler/container" ) @@ -30,10 +31,17 @@ var asyncPurgeRecordOnce sync.Once // AllScheduleActionRecords query the schedule action records with the specified offset, limit, and time range func AllScheduleActionRecords(ctx context.Context, start, end int64, offset, limit int, dic *di.Container) (scheduleActionRecordDTOs []dtos.ScheduleActionRecord, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - records, err := dbClient.AllScheduleActionRecords(ctx, start, end, offset, limit) - if err == nil { - totalCount, err = dbClient.ScheduleActionRecordTotalCount(ctx, start, end) + + totalCount, err = dbClient.ScheduleActionRecordTotalCount(ctx, start, end) + if err != nil { + return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.ScheduleActionRecord{}, totalCount, err + } + + records, err := dbClient.AllScheduleActionRecords(ctx, start, end, offset, limit) if err != nil { return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -43,12 +51,22 @@ func AllScheduleActionRecords(ctx context.Context, start, end int64, offset, lim } // ScheduleActionRecordsByStatus query the schedule action records with the specified status, offset, limit, and time range -func ScheduleActionRecordsByStatus(ctx context.Context, status string, start, end int64, offset, limit int, dic *di.Container) (scheduleActionRecordDTOs []dtos.ScheduleActionRecord, totalCount uint32, edgeXerr errors.EdgeX) { +func ScheduleActionRecordsByStatus(ctx context.Context, status string, start, end int64, offset, limit int, dic *di.Container) (scheduleActionRecordDTOs []dtos.ScheduleActionRecord, totalCount uint32, err errors.EdgeX) { + if status == "" { + return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "status is empty", nil) + } + dbClient := container.DBClientFrom(dic.Get) - records, err := dbClient.ScheduleActionRecordsByStatus(ctx, status, start, end, offset, limit) - if err == nil { - totalCount, err = dbClient.ScheduleActionRecordCountByStatus(ctx, status, start, end) + totalCount, err = dbClient.ScheduleActionRecordCountByStatus(ctx, status, start, end) + if err != nil { + return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.ScheduleActionRecord{}, totalCount, err + } + + records, err := dbClient.ScheduleActionRecordsByStatus(ctx, status, start, end, offset, limit) if err != nil { return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -58,12 +76,22 @@ func ScheduleActionRecordsByStatus(ctx context.Context, status string, start, en } // ScheduleActionRecordsByJobName query the schedule action records with the specified job name, offset, limit, and time range -func ScheduleActionRecordsByJobName(ctx context.Context, jobName string, start, end int64, offset, limit int, dic *di.Container) (scheduleActionRecordDTOs []dtos.ScheduleActionRecord, totalCount uint32, edgeXerr errors.EdgeX) { +func ScheduleActionRecordsByJobName(ctx context.Context, jobName string, start, end int64, offset, limit int, dic *di.Container) (scheduleActionRecordDTOs []dtos.ScheduleActionRecord, totalCount uint32, err errors.EdgeX) { + if jobName == "" { + return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "jobName is empty", nil) + } + dbClient := container.DBClientFrom(dic.Get) - records, err := dbClient.ScheduleActionRecordsByJobName(ctx, jobName, start, end, offset, limit) - if err == nil { - totalCount, err = dbClient.ScheduleActionRecordCountByJobName(ctx, jobName, start, end) + totalCount, err = dbClient.ScheduleActionRecordCountByJobName(ctx, jobName, start, end) + if err != nil { + return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.ScheduleActionRecord{}, totalCount, err + } + + records, err := dbClient.ScheduleActionRecordsByJobName(ctx, jobName, start, end, offset, limit) if err != nil { return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -73,12 +101,25 @@ func ScheduleActionRecordsByJobName(ctx context.Context, jobName string, start, } // ScheduleActionRecordsByJobNameAndStatus query the schedule action records with the specified job name, status, offset, limit, and time range -func ScheduleActionRecordsByJobNameAndStatus(ctx context.Context, jobName, status string, start, end int64, offset, limit int, dic *di.Container) (scheduleActionRecordDTOs []dtos.ScheduleActionRecord, totalCount uint32, edgeXerr errors.EdgeX) { +func ScheduleActionRecordsByJobNameAndStatus(ctx context.Context, jobName, status string, start, end int64, offset, limit int, dic *di.Container) (scheduleActionRecordDTOs []dtos.ScheduleActionRecord, totalCount uint32, err errors.EdgeX) { + if jobName == "" { + return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "jobName is empty", nil) + } + if status == "" { + return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "status is empty", nil) + } + dbClient := container.DBClientFrom(dic.Get) - records, err := dbClient.ScheduleActionRecordsByJobNameAndStatus(ctx, jobName, status, start, end, offset, limit) - if err == nil { - totalCount, err = dbClient.ScheduleActionRecordCountByJobNameAndStatus(ctx, jobName, status, start, end) + totalCount, err = dbClient.ScheduleActionRecordCountByJobNameAndStatus(ctx, jobName, status, start, end) + if err != nil { + return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.ScheduleActionRecord{}, totalCount, err } + + records, err := dbClient.ScheduleActionRecordsByJobNameAndStatus(ctx, jobName, status, start, end, offset, limit) if err != nil { return scheduleActionRecordDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) } diff --git a/internal/support/cronscheduler/application/schedulejob.go b/internal/support/cronscheduler/application/schedulejob.go index 4f4791b951..b73ceae5df 100644 --- a/internal/support/cronscheduler/application/schedulejob.go +++ b/internal/support/cronscheduler/application/schedulejob.go @@ -8,6 +8,7 @@ package application import ( "context" "fmt" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" "time" bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" @@ -86,10 +87,17 @@ func ScheduleJobByName(ctx context.Context, name string, dic *di.Container) (dto // AllScheduleJobs queries all the schedule jobs with offset and limit func AllScheduleJobs(ctx context.Context, labels []string, offset, limit int, dic *di.Container) (scheduleJobDTOs []dtos.ScheduleJob, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - jobs, err := dbClient.AllScheduleJobs(ctx, labels, offset, limit) - if err == nil { - totalCount, err = dbClient.ScheduleJobTotalCount(ctx, labels) + + totalCount, err = dbClient.ScheduleJobTotalCount(ctx, labels) + if err != nil { + return scheduleJobDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.ScheduleJob{}, totalCount, err + } + + jobs, err := dbClient.AllScheduleJobs(ctx, labels, offset, limit) if err != nil { return scheduleJobDTOs, totalCount, errors.NewCommonEdgeXWrapper(err) } diff --git a/internal/support/cronscheduler/controller/http/scheduleactionrecord_test.go b/internal/support/cronscheduler/controller/http/scheduleactionrecord_test.go index 6fbbc2bf56..6435912d99 100644 --- a/internal/support/cronscheduler/controller/http/scheduleactionrecord_test.go +++ b/internal/support/cronscheduler/controller/http/scheduleactionrecord_test.go @@ -52,15 +52,15 @@ func scheduleActionRecordsData() []dtos.ScheduleActionRecord { } func TestAllScheduleActionRecords(t *testing.T) { - expectedTotalScheduleActionRecordCount := uint32(0) + expectedTotalScheduleActionRecordCount := uint32(2) dic := mockDic() dbClientMock := &csMock.DBClient{} dbClientMock.On("ScheduleActionRecordTotalCount", context.Background(), int64(0), mock.AnythingOfType("int64")).Return(expectedTotalScheduleActionRecordCount, nil) dbClientMock.On("AllScheduleActionRecords", context.Background(), int64(0), mock.AnythingOfType("int64"), 0, 20).Return([]models.ScheduleActionRecord{}, nil) dbClientMock.On("AllScheduleActionRecords", context.Background(), int64(0), mock.AnythingOfType("int64"), 0, 1).Return([]models.ScheduleActionRecord{}, nil) - dbClientMock.On("AllScheduleActionRecords", context.Background(), int64(0), int64(1723642440000), 0, 1).Return([]models.ScheduleActionRecord{}, nil) dbClientMock.On("AllScheduleActionRecords", context.Background(), int64(1723642430000), int64(1723642440000), 0, 1).Return([]models.ScheduleActionRecord{}, nil) dbClientMock.On("ScheduleActionRecordTotalCount", context.Background(), int64(1723642430000), int64(1723642440000)).Return(expectedTotalScheduleActionRecordCount, nil) + dbClientMock.On("AllScheduleActionRecords", context.Background(), int64(0), mock.AnythingOfType("int64"), 4, 2).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) any { return dbClientMock @@ -86,6 +86,7 @@ func TestAllScheduleActionRecords(t *testing.T) { {"Invalid - invalid start, end must be greater than start", "1723642440000", "0", "", "", true, expectedTotalScheduleActionRecordCount, http.StatusBadRequest}, {"Invalid - invalid offset format", "", "", "aaa", "1", true, expectedTotalScheduleActionRecordCount, http.StatusBadRequest}, {"Invalid - invalid limit format", "", "", "1", "aaa", true, expectedTotalScheduleActionRecordCount, http.StatusBadRequest}, + {"Invalid - offset out of range", "", "", "4", "2", true, expectedTotalScheduleActionRecordCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { @@ -220,8 +221,8 @@ func TestScheduleActionRecordsByStatus(t *testing.T) { dbClientMock := &csMock.DBClient{} dbClientMock.On("ScheduleActionRecordCountByStatus", context.Background(), testStatus, int64(0), mock.AnythingOfType("int64")).Return(expectedTotalScheduleActionRecordCount, nil) dbClientMock.On("ScheduleActionRecordsByStatus", context.Background(), testStatus, int64(0), mock.AnythingOfType("int64"), 0, 20).Return(records, nil) - dbClientMock.On("ScheduleActionRecordsByStatus", context.Background(), emptyStatus, int64(0), mock.AnythingOfType("int64"), 0, 20).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindContractInvalid, "the status is required", nil)) - dbClientMock.On("ScheduleActionRecordsByStatus", context.Background(), notFoundStatus, int64(0), mock.AnythingOfType("int64"), 0, 20).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "schedule action records with given status doesn't exist in the database", nil)) + dbClientMock.On("ScheduleActionRecordCountByStatus", context.Background(), notFoundStatus, int64(0), mock.AnythingOfType("int64")).Return(expectedTotalScheduleActionRecordCount, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "schedule action records with given status doesn't exist in the database", nil)) + dbClientMock.On("ScheduleActionRecordsByStatus", context.Background(), testStatus, int64(0), mock.AnythingOfType("int64"), 4, 2).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) any { return dbClientMock @@ -245,12 +246,27 @@ func TestScheduleActionRecordsByStatus(t *testing.T) { {"Valid - find schedule action records by status", testStatus, "", "", "", "", false, expectedTotalScheduleActionRecordCount, http.StatusOK}, {"Invalid - status parameter is empty", emptyStatus, "", "", "", "", true, expectedTotalScheduleActionRecordCount, http.StatusBadRequest}, {"Invalid - schedule action records not found by status", notFoundStatus, "", "", "", "", true, expectedTotalScheduleActionRecordCount, http.StatusNotFound}, + {"Invalid - offset out of range", testStatus, "", "", "4", "2", true, expectedTotalScheduleActionRecordCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { e := echo.New() reqPath := fmt.Sprintf("%s/%s", common.ApiScheduleActionRecordRouteByStatusEchoRoute, testCase.status) req, err := http.NewRequest(http.MethodGet, reqPath, http.NoBody) + query := req.URL.Query() + if testCase.start != "" { + query.Add(common.Start, testCase.start) + } + if testCase.end != "" { + query.Add(common.End, testCase.end) + } + if testCase.offset != "" { + query.Add(common.Offset, testCase.offset) + } + if testCase.limit != "" { + query.Add(common.Limit, testCase.limit) + } + req.URL.RawQuery = query.Encode() require.NoError(t, err) // Act @@ -299,8 +315,8 @@ func TestScheduleActionRecordsByJobName(t *testing.T) { dbClientMock := &csMock.DBClient{} dbClientMock.On("ScheduleActionRecordCountByJobName", context.Background(), testScheduleJobName, int64(0), mock.AnythingOfType("int64")).Return(expectedTotalScheduleActionRecordCount, nil) dbClientMock.On("ScheduleActionRecordsByJobName", context.Background(), testScheduleJobName, int64(0), mock.AnythingOfType("int64"), 0, 20).Return(records, nil) - dbClientMock.On("ScheduleActionRecordsByJobName", context.Background(), emptyJobName, int64(0), mock.AnythingOfType("int64"), 0, 20).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindContractInvalid, "the job name is required", nil)) - dbClientMock.On("ScheduleActionRecordsByJobName", context.Background(), notFoundJobName, int64(0), mock.AnythingOfType("int64"), 0, 20).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "schedule action records with given job name doesn't exist in the database", nil)) + dbClientMock.On("ScheduleActionRecordCountByJobName", context.Background(), notFoundJobName, int64(0), mock.AnythingOfType("int64")).Return(expectedTotalScheduleActionRecordCount, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "schedule action records with given job name doesn't exist in the database", nil)) + dbClientMock.On("ScheduleActionRecordsByJobName", context.Background(), testScheduleJobName, int64(0), mock.AnythingOfType("int64"), 4, 2).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) any { return dbClientMock @@ -324,12 +340,27 @@ func TestScheduleActionRecordsByJobName(t *testing.T) { {"Valid - find schedule action records by job name", testScheduleJobName, "", "", "", "", false, expectedTotalScheduleActionRecordCount, http.StatusOK}, {"Invalid - job name parameter is empty", emptyJobName, "", "", "", "", true, expectedTotalScheduleActionRecordCount, http.StatusBadRequest}, {"Invalid - schedule action records not found by job name", notFoundJobName, "", "", "", "", true, expectedTotalScheduleActionRecordCount, http.StatusNotFound}, + {"Invalid - offset out of range", testScheduleJobName, "", "", "4", "2", true, expectedTotalScheduleActionRecordCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { e := echo.New() reqPath := fmt.Sprintf("%s/%s", common.ApiScheduleActionRecordRouteByJobNameEchoRoute, testCase.jobName) req, err := http.NewRequest(http.MethodGet, reqPath, http.NoBody) + query := req.URL.Query() + if testCase.start != "" { + query.Add(common.Start, testCase.start) + } + if testCase.end != "" { + query.Add(common.End, testCase.end) + } + if testCase.offset != "" { + query.Add(common.Offset, testCase.offset) + } + if testCase.limit != "" { + query.Add(common.Limit, testCase.limit) + } + req.URL.RawQuery = query.Encode() require.NoError(t, err) // Act @@ -380,8 +411,8 @@ func TestScheduleActionRecordsByJobNameAndStatus(t *testing.T) { dbClientMock := &csMock.DBClient{} dbClientMock.On("ScheduleActionRecordCountByJobNameAndStatus", context.Background(), testScheduleJobName, testStatus, int64(0), mock.AnythingOfType("int64")).Return(expectedTotalScheduleActionRecordCount, nil) dbClientMock.On("ScheduleActionRecordsByJobNameAndStatus", context.Background(), testScheduleJobName, testStatus, int64(0), mock.AnythingOfType("int64"), 0, 20).Return(records, nil) - dbClientMock.On("ScheduleActionRecordsByJobNameAndStatus", context.Background(), emptyJobName, emptyStatus, int64(0), mock.AnythingOfType("int64"), 0, 20).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindContractInvalid, "the job name and status are required", nil)) - dbClientMock.On("ScheduleActionRecordsByJobNameAndStatus", context.Background(), notFoundJobName, notFoundStatus, int64(0), mock.AnythingOfType("int64"), 0, 20).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "schedule action records with given job name and status doesn't exist in the database", nil)) + dbClientMock.On("ScheduleActionRecordCountByJobNameAndStatus", context.Background(), notFoundJobName, notFoundStatus, int64(0), mock.AnythingOfType("int64")).Return(expectedTotalScheduleActionRecordCount, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "schedule action records with given job name and status doesn't exist in the database", nil)) + dbClientMock.On("ScheduleActionRecordsByJobNameAndStatus", context.Background(), testScheduleJobName, testStatus, int64(0), mock.AnythingOfType("int64"), 4, 2).Return([]models.ScheduleActionRecord{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) any { return dbClientMock @@ -406,12 +437,27 @@ func TestScheduleActionRecordsByJobNameAndStatus(t *testing.T) { {"Valid - find schedule action records by job name", testScheduleJobName, testStatus, "", "", "", "", false, expectedTotalScheduleActionRecordCount, http.StatusOK}, {"Invalid - job name and status parameters are empty", emptyJobName, emptyStatus, "", "", "", "", true, expectedTotalScheduleActionRecordCount, http.StatusBadRequest}, {"Invalid - schedule action records not found by job name and status", notFoundJobName, notFoundStatus, "", "", "", "", true, expectedTotalScheduleActionRecordCount, http.StatusNotFound}, + {"Invalid - offset out of range", testScheduleJobName, testStatus, "", "", "4", "2", true, expectedTotalScheduleActionRecordCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { e := echo.New() reqPath := fmt.Sprintf("%s/%s/%s/:%s/%s", common.ApiScheduleActionRecordRouteByJobNameEchoRoute, testCase.jobName, common.Status, common.Status, testCase.status) req, err := http.NewRequest(http.MethodGet, reqPath, http.NoBody) + query := req.URL.Query() + if testCase.start != "" { + query.Add(common.Start, testCase.start) + } + if testCase.end != "" { + query.Add(common.End, testCase.end) + } + if testCase.offset != "" { + query.Add(common.Offset, testCase.offset) + } + if testCase.limit != "" { + query.Add(common.Limit, testCase.limit) + } + req.URL.RawQuery = query.Encode() require.NoError(t, err) // Act diff --git a/internal/support/notifications/application/notification.go b/internal/support/notifications/application/notification.go index 9eef47268c..73f09cc693 100644 --- a/internal/support/notifications/application/notification.go +++ b/internal/support/notifications/application/notification.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2021-2023 IOTech Ltd +// Copyright (C) 2021-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -12,6 +12,7 @@ import ( "time" "github.com/edgexfoundry/edgex-go/internal/pkg/correlation" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" "github.com/edgexfoundry/edgex-go/internal/support/notifications/container" bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" @@ -50,11 +51,18 @@ func NotificationsByCategory(offset, limit int, category string, dic *di.Contain if category == "" { return notifications, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "category is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - notificationModels, err := dbClient.NotificationsByCategory(offset, limit, category) - if err == nil { - totalCount, err = dbClient.NotificationCountByCategory(category) + totalCount, err = dbClient.NotificationCountByCategory(category) + if err != nil { + return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Notification{}, totalCount, err } + + notificationModels, err := dbClient.NotificationsByCategory(offset, limit, category) if err != nil { return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -70,11 +78,18 @@ func NotificationsByLabel(offset, limit int, label string, dic *di.Container) (n if label == "" { return notifications, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "label is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - notificationModels, err := dbClient.NotificationsByLabel(offset, limit, label) - if err == nil { - totalCount, err = dbClient.NotificationCountByLabel(label) + totalCount, err = dbClient.NotificationCountByLabel(label) + if err != nil { + return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Notification{}, totalCount, err } + + notificationModels, err := dbClient.NotificationsByLabel(offset, limit, label) if err != nil { return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -108,11 +123,18 @@ func NotificationsByStatus(offset, limit int, status string, dic *di.Container) if status == "" { return notifications, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "status is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - notificationModels, err := dbClient.NotificationsByStatus(offset, limit, status) - if err == nil { - totalCount, err = dbClient.NotificationCountByStatus(status) + totalCount, err = dbClient.NotificationCountByStatus(status) + if err != nil { + return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Notification{}, totalCount, err + } + + notificationModels, err := dbClient.NotificationsByStatus(offset, limit, status) if err != nil { return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -126,10 +148,17 @@ func NotificationsByStatus(offset, limit int, status string, dic *di.Container) // NotificationsByTimeRange query notifications with offset, limit and time range func NotificationsByTimeRange(start int64, end int64, offset int, limit int, dic *di.Container) (notifications []dtos.Notification, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - notificationModels, err := dbClient.NotificationsByTimeRange(start, end, offset, limit) - if err == nil { - totalCount, err = dbClient.NotificationCountByTimeRange(start, end) + + totalCount, err = dbClient.NotificationCountByTimeRange(start, end) + if err != nil { + return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Notification{}, totalCount, err + } + + notificationModels, err := dbClient.NotificationsByTimeRange(start, end, offset, limit) if err != nil { return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -162,15 +191,22 @@ func NotificationsBySubscriptionName(offset, limit int, subscriptionName string, if subscriptionName == "" { return notifications, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "subscriptionName is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) subscription, err := dbClient.SubscriptionByName(subscriptionName) if err != nil { return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) } - notificationModels, err := dbClient.NotificationsByCategoriesAndLabels(offset, limit, subscription.Categories, subscription.Labels) - if err == nil { - totalCount, err = dbClient.NotificationCountByCategoriesAndLabels(subscription.Categories, subscription.Labels) + totalCount, err = dbClient.NotificationCountByCategoriesAndLabels(subscription.Categories, subscription.Labels) + if err != nil { + return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Notification{}, totalCount, err } + + notificationModels, err := dbClient.NotificationsByCategoriesAndLabels(offset, limit, subscription.Categories, subscription.Labels) if err != nil { return notifications, totalCount, errors.NewCommonEdgeXWrapper(err) } diff --git a/internal/support/notifications/application/subscription.go b/internal/support/notifications/application/subscription.go index a8e32c2a4a..aa02805719 100644 --- a/internal/support/notifications/application/subscription.go +++ b/internal/support/notifications/application/subscription.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2020-2021 IOTech Ltd +// Copyright (C) 2020-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -10,6 +10,7 @@ import ( "fmt" "github.com/edgexfoundry/edgex-go/internal/pkg/correlation" + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" "github.com/edgexfoundry/edgex-go/internal/support/notifications/container" "github.com/edgexfoundry/edgex-go/internal/support/notifications/infrastructure/interfaces" @@ -43,10 +44,17 @@ func AddSubscription(d models.Subscription, ctx context.Context, dic *di.Contain // AllSubscriptions queries subscriptions by offset and limit func AllSubscriptions(offset, limit int, dic *di.Container) (subscriptions []dtos.Subscription, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - subs, err := dbClient.AllSubscriptions(offset, limit) - if err == nil { - totalCount, err = dbClient.SubscriptionTotalCount() + + totalCount, err = dbClient.SubscriptionTotalCount() + if err != nil { + return subscriptions, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Subscription{}, totalCount, err + } + + subs, err := dbClient.AllSubscriptions(offset, limit) if err != nil { return subscriptions, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -76,11 +84,18 @@ func SubscriptionsByCategory(offset, limit int, category string, dic *di.Contain if category == "" { return subscriptions, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "category is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - subscriptionModels, err := dbClient.SubscriptionsByCategory(offset, limit, category) - if err == nil { - totalCount, err = dbClient.SubscriptionCountByCategory(category) + totalCount, err = dbClient.SubscriptionCountByCategory(category) + if err != nil { + return subscriptions, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Subscription{}, totalCount, err + } + + subscriptionModels, err := dbClient.SubscriptionsByCategory(offset, limit, category) if err != nil { return subscriptions, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -96,11 +111,18 @@ func SubscriptionsByLabel(offset, limit int, label string, dic *di.Container) (s if label == "" { return subscriptions, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "label is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - subscriptionModels, err := dbClient.SubscriptionsByLabel(offset, limit, label) - if err == nil { - totalCount, err = dbClient.SubscriptionCountByLabel(label) + totalCount, err = dbClient.SubscriptionCountByLabel(label) + if err != nil { + return subscriptions, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Subscription{}, totalCount, err + } + + subscriptionModels, err := dbClient.SubscriptionsByLabel(offset, limit, label) if err != nil { return subscriptions, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -116,11 +138,18 @@ func SubscriptionsByReceiver(offset, limit int, receiver string, dic *di.Contain if receiver == "" { return subscriptions, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "receiver is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - subscriptionModels, err := dbClient.SubscriptionsByReceiver(offset, limit, receiver) - if err == nil { - totalCount, err = dbClient.SubscriptionCountByReceiver(receiver) + totalCount, err = dbClient.SubscriptionCountByReceiver(receiver) + if err != nil { + return subscriptions, totalCount, errors.NewCommonEdgeXWrapper(err) } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Subscription{}, totalCount, err + } + + subscriptionModels, err := dbClient.SubscriptionsByReceiver(offset, limit, receiver) if err != nil { return subscriptions, totalCount, errors.NewCommonEdgeXWrapper(err) } diff --git a/internal/support/notifications/application/transmission.go b/internal/support/notifications/application/transmission.go index 6f4d7c6a60..320ccb2187 100644 --- a/internal/support/notifications/application/transmission.go +++ b/internal/support/notifications/application/transmission.go @@ -1,11 +1,12 @@ // -// Copyright (C) 2021 IOTech Ltd +// Copyright (C) 2021-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 package application import ( + "github.com/edgexfoundry/edgex-go/internal/pkg/utils" "github.com/edgexfoundry/edgex-go/internal/support/notifications/container" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" @@ -36,10 +37,17 @@ func TransmissionById(id string, dic *di.Container) (trans dtos.Transmission, ed // TransmissionsByTimeRange query transmissions with offset, limit and time range func TransmissionsByTimeRange(start int64, end int64, offset int, limit int, dic *di.Container) (transmissions []dtos.Transmission, totalCount uint32, err errors.EdgeX) { dbClient := container.DBClientFrom(dic.Get) - models, err := dbClient.TransmissionsByTimeRange(start, end, offset, limit) - if err == nil { - totalCount, err = dbClient.TransmissionCountByTimeRange(start, end) + + totalCount, err = dbClient.TransmissionCountByTimeRange(start, end) + if err != nil { + return transmissions, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Transmission{}, totalCount, err } + + models, err := dbClient.TransmissionsByTimeRange(start, end, offset, limit) if err != nil { return transmissions, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -72,11 +80,18 @@ func TransmissionsByStatus(offset, limit int, status string, dic *di.Container) if status == "" { return transmissions, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "status is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - transModels, err := dbClient.TransmissionsByStatus(offset, limit, status) - if err == nil { - totalCount, err = dbClient.TransmissionCountByStatus(status) + totalCount, err = dbClient.TransmissionCountByStatus(status) + if err != nil { + return transmissions, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Transmission{}, totalCount, err } + + transModels, err := dbClient.TransmissionsByStatus(offset, limit, status) if err != nil { return transmissions, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -100,11 +115,18 @@ func TransmissionsBySubscriptionName(offset, limit int, subscriptionName string, if subscriptionName == "" { return transmissions, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "subscription name is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - transModels, err := dbClient.TransmissionsBySubscriptionName(offset, limit, subscriptionName) - if err == nil { - totalCount, err = dbClient.TransmissionCountBySubscriptionName(subscriptionName) + totalCount, err = dbClient.TransmissionCountBySubscriptionName(subscriptionName) + if err != nil { + return transmissions, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Transmission{}, totalCount, err } + + transModels, err := dbClient.TransmissionsBySubscriptionName(offset, limit, subscriptionName) if err != nil { return transmissions, totalCount, errors.NewCommonEdgeXWrapper(err) } @@ -116,11 +138,18 @@ func TransmissionsByNotificationId(offset, limit int, notificationId string, dic if notificationId == "" { return transmissions, totalCount, errors.NewCommonEdgeX(errors.KindContractInvalid, "notification id is empty", nil) } + dbClient := container.DBClientFrom(dic.Get) - transModels, err := dbClient.TransmissionsByNotificationId(offset, limit, notificationId) - if err == nil { - totalCount, err = dbClient.TransmissionCountByNotificationId(notificationId) + totalCount, err = dbClient.TransmissionCountByNotificationId(notificationId) + if err != nil { + return transmissions, totalCount, errors.NewCommonEdgeXWrapper(err) + } + cont, err := utils.CheckCountRange(totalCount, offset, limit) + if !cont { + return []dtos.Transmission{}, totalCount, err } + + transModels, err := dbClient.TransmissionsByNotificationId(offset, limit, notificationId) if err != nil { return transmissions, totalCount, errors.NewCommonEdgeXWrapper(err) } else { diff --git a/internal/support/notifications/controller/http/subscription_test.go b/internal/support/notifications/controller/http/subscription_test.go index 76f1ca3791..e29c990cc8 100644 --- a/internal/support/notifications/controller/http/subscription_test.go +++ b/internal/support/notifications/controller/http/subscription_test.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2020-2023 IOTech Ltd +// Copyright (C) 2020-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -217,7 +217,7 @@ func TestAllSubscriptions(t *testing.T) { dbClientMock.On("SubscriptionTotalCount").Return(expectedSubscriptionCount, nil) dbClientMock.On("AllSubscriptions", 0, 20).Return(subscriptions, nil) dbClientMock.On("AllSubscriptions", 1, 2).Return([]models.Subscription{subscriptions[1], subscriptions[2]}, nil) - dbClientMock.On("AllSubscriptions", 4, 1).Return([]models.Subscription{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil)) + dbClientMock.On("AllSubscriptions", 4, 1).Return([]models.Subscription{}, errors.NewCommonEdgeX(errors.KindRangeNotSatisfiable, "query objects bounds out of range.", nil)) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -237,7 +237,7 @@ func TestAllSubscriptions(t *testing.T) { }{ {"Valid - get subscriptions without offset and limit", "", "", false, 3, expectedSubscriptionCount, http.StatusOK}, {"Valid - get subscriptions with offset and limit", "1", "2", false, 2, expectedSubscriptionCount, http.StatusOK}, - {"Invalid - offset out of range", "4", "1", true, 0, expectedSubscriptionCount, http.StatusNotFound}, + {"Invalid - offset out of range", "4", "1", true, 0, expectedSubscriptionCount, http.StatusRequestedRangeNotSatisfiable}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { diff --git a/openapi/v3/core-metadata.yaml b/openapi/v3/core-metadata.yaml index 6e7905e5bb..3c95e82652 100644 --- a/openapi/v3/core-metadata.yaml +++ b/openapi/v3/core-metadata.yaml @@ -1191,6 +1191,11 @@ components: requestId: "8a41b3f4-0148-11eb-adc1-0242ac120002" statusCode: 409 message: "Data Duplicate" + 416Example: + value: + apiVersion: "v3" + statusCode: 416 + message: "Range Not Satisfiable" 409DeleteExample: value: apiVersion: "v3" @@ -1810,6 +1815,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -2041,6 +2058,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -2091,6 +2120,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -2401,6 +2442,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -2652,6 +2705,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -3101,6 +3166,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -3157,6 +3234,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -3207,6 +3296,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -3458,6 +3559,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -3751,6 +3864,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -3929,6 +4054,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: @@ -3979,6 +4116,18 @@ paths: examples: 400Example: $ref: '#/components/examples/400Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "Internal Server Error" headers: diff --git a/openapi/v3/support-cron-scheduler.yaml b/openapi/v3/support-cron-scheduler.yaml index 2dd3fa6cc1..949ba86c69 100644 --- a/openapi/v3/support-cron-scheduler.yaml +++ b/openapi/v3/support-cron-scheduler.yaml @@ -510,6 +510,11 @@ components: requestId: "84c9489c-0148-11eb-adc1-0242ac120002" statusCode: 404 message: "Not Found" + 416Example: + value: + apiVersion: "v3" + statusCode: 416 + message: "Range Not Satisfiable" 500Example: value: apiVersion: "v3" @@ -860,6 +865,18 @@ paths: examples: MultiScheduleJobsExample: $ref: '#/components/examples/MultiScheduleJobsExample' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "An unexpected error occurred on the server" headers: @@ -1018,6 +1035,18 @@ paths: examples: MultiScheduleActionRecordsExample: $ref: '#/components/examples/MultiScheduleActionRecordsExample' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "An unexpected error occurred on the server" headers: @@ -1100,6 +1129,18 @@ paths: examples: SingleScheduleActionRecordExample: $ref: '#/components/examples/SingleScheduleActionRecordExample' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "An unexpected error occurred on the server" headers: @@ -1149,6 +1190,18 @@ paths: examples: 404Example: $ref: '#/components/examples/404Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "An unexpected error occurred on the server" headers: @@ -1197,6 +1250,18 @@ paths: examples: 404Example: $ref: '#/components/examples/404Example' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "An unexpected error occurred on the server" headers: diff --git a/openapi/v3/support-scheduler.yaml b/openapi/v3/support-scheduler.yaml index 7a3355c86e..0a0aa593da 100644 --- a/openapi/v3/support-scheduler.yaml +++ b/openapi/v3/support-scheduler.yaml @@ -463,6 +463,11 @@ components: apiVersion: "v3" statusCode: 400 message: "Bad Request" + 416Example: + value: + apiVersion: "v3" + statusCode: 416 + message: "Range Not Satisfiable" 500Example: value: apiVersion: "v3" @@ -638,6 +643,18 @@ paths: examples: MultiIntervalsExample: $ref: '#/components/examples/MultiIntervalsExample' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "An unexpected error occurred on the server" headers: @@ -833,6 +850,18 @@ paths: examples: MultiIntervalActionsExample: $ref: '#/components/examples/MultiIntervalActionsExample' + '416': + description: "Request range is not satisfiable" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + 416Example: + $ref: '#/components/examples/416Example' '500': description: "An unexpected error occurred on the server" headers: