Skip to content

Commit

Permalink
Merge pull request #513 from FelixTing/issue-510
Browse files Browse the repository at this point in the history
feat(notifications): Add Notification DTO and Model
  • Loading branch information
cloudxxx8 authored Feb 25, 2021
2 parents 0d1d839 + a60e686 commit caf1e88
Show file tree
Hide file tree
Showing 14 changed files with 452 additions and 50 deletions.
15 changes: 15 additions & 0 deletions v2/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ const (
ApiSubscriptionByLabelRoute = ApiSubscriptionRoute + "/" + Label + "/{" + Label + "}"
ApiSubscriptionByReceiverRoute = ApiSubscriptionRoute + "/" + Receiver + "/{" + Receiver + "}"

ApiNotificationCleanupRoute = ApiBase + "/cleanup"
ApiNotificationCleanupByAgeRoute = ApiBase + "/" + Cleanup + "/" + Age + "/{" + Age + "}"
ApiNotificationRoute = ApiBase + "/notification"
ApiNotificationByTimeRangeRoute = ApiNotificationRoute + "/" + Start + "/{" + Start + "}/" + End + "/{" + End + "}"
ApiNotificationByAgeRoute = ApiNotificationRoute + "/" + Age + "/{" + Age + "}"
ApiNotificationByCategoryRoute = ApiNotificationRoute + "/" + Category + "/{" + Category + "}"
ApiNotificationByLabelRoute = ApiNotificationRoute + "/" + Label + "/{" + Label + "}"
ApiNotificationByIdRoute = ApiNotificationRoute + "/" + Id + "/{" + Id + "}"
ApiNotificationByStatusRoute = ApiNotificationRoute + "/" + Status + "/{" + Status + "}"
ApiNotificationBySubscriptionNameRoute = ApiNotificationRoute + "/" + Subscription + "/" + Name + "/{" + Name + "}"

ApiConfigRoute = ApiBase + "/config"
ApiMetricsRoute = ApiBase + "/metrics"
ApiPingRoute = ApiBase + "/ping"
Expand Down Expand Up @@ -123,6 +134,10 @@ const (
Receiver = "receiver"
Subscription = "subscription"
Target = "target"
Status = "status"
Cleanup = "cleanup"
Sender = "sender"
Severity = "severity"

Offset = "offset" //query string to specify the number of items to skip before starting to collect the result set.
Limit = "limit" //query string to specify the numbers of items to return
Expand Down
81 changes: 81 additions & 0 deletions v2/dtos/notification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// Copyright (C) 2021 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package dtos

import (
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos/common"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models"
)

// Notification and its properties are defined in the APIv2 specification:
// https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/support-notifications/2.x#/Notification
type Notification struct {
common.Versionable `json:",inline"`
Id string `json:"id,omitempty" validate:"omitempty,uuid"`
Created int64 `json:"created,omitempty"`
Modified int64 `json:"modified,omitempty"`
Category string `json:"category,omitempty" validate:"required_without=Labels,omitempty,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Labels []string `json:"labels,omitempty" validate:"required_without=Category,omitempty,gt=0,dive,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Content string `json:"content" validate:"required,edgex-dto-none-empty-string"`
ContentType string `json:"contentType,omitempty"`
Description string `json:"description,omitempty"`
Sender string `json:"sender" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Severity string `json:"severity" validate:"required,oneof='MINOR' 'NORMAL' 'CRITICAL'"`
Status string `json:"status,omitempty" validate:"omitempty,oneof='NEW' 'PROCESSED' 'ESCALATED'"`
}

// ToNotificationModel transforms the Notification DTO to the Notification Model
func ToNotificationModel(n Notification) models.Notification {
var m models.Notification
m.Id = n.Id
m.Created = n.Created
m.Modified = n.Modified
m.Category = n.Category
m.Labels = n.Labels
m.Content = n.Content
m.ContentType = n.ContentType
m.Description = n.Description
m.Sender = n.Sender
m.Severity = models.NotificationSeverity(n.Severity)
m.Status = models.NotificationStatus(n.Status)
return m
}

// ToNotificationModels transforms the Notification DTO array to the Notification model array
func ToNotificationModels(notifications []Notification) []models.Notification {
models := make([]models.Notification, len(notifications))
for i, n := range notifications {
models[i] = ToNotificationModel(n)
}
return models
}

// FromNotificationModelToDTO transforms the Notification Model to the Notification DTO
func FromNotificationModelToDTO(n models.Notification) Notification {
return Notification{
Versionable: common.NewVersionable(),
Id: n.Id,
Created: n.Created,
Modified: n.Modified,
Category: string(n.Category),
Labels: n.Labels,
Content: n.Content,
ContentType: n.ContentType,
Description: n.Description,
Sender: n.Sender,
Severity: string(n.Severity),
Status: string(n.Status),
}
}

// FromNotificationModelsToDTOs transforms the Notification model array to the Notification DTO array
func FromNotificationModelsToDTOs(notifications []models.Notification) []Notification {
dtos := make([]Notification, len(notifications))
for i, n := range notifications {
dtos[i] = FromNotificationModelToDTO(n)
}
return dtos
}
66 changes: 66 additions & 0 deletions v2/dtos/requests/notification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Copyright (C) 2021 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package requests

import (
"encoding/json"

"github.com/edgexfoundry/go-mod-core-contracts/v2/errors"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos/common"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models"
)

// AddNotificationRequest defines the Request Content for POST Notification DTO.
// This object and its properties correspond to the AddNotificationRequest object in the APIv2 specification:
// https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/support-notifications/2.x#/AddNotificationRequest
type AddNotificationRequest struct {
common.BaseRequest `json:",inline"`
Notification dtos.Notification `json:"notification"`
}

// Validate satisfies the Validator interface
func (request AddNotificationRequest) Validate() error {
err := v2.Validate(request)
return err
}

// UnmarshalJSON implements the Unmarshaler interface for the AddNotificationRequest type
func (request *AddNotificationRequest) UnmarshalJSON(b []byte) error {
var alias struct {
common.BaseRequest
Notification dtos.Notification
}
if err := json.Unmarshal(b, &alias); err != nil {
return errors.NewCommonEdgeX(errors.KindContractInvalid, "Failed to unmarshal request body as JSON.", err)
}

*request = AddNotificationRequest(alias)

// validate AddNotificationRequest DTO
if err := request.Validate(); err != nil {
return err
}
return nil
}

// AddNotificationReqToNotificationModels transforms the AddNotificationRequest DTO array to the AddNotificationRequest model array
func AddNotificationReqToNotificationModels(reqs []AddNotificationRequest) (n []models.Notification) {
for _, req := range reqs {
d := dtos.ToNotificationModel(req.Notification)
n = append(n, d)
}
return n
}

func NewAddNotificationRequest(dto dtos.Notification) AddNotificationRequest {
dto.Versionable = common.NewVersionable()
return AddNotificationRequest{
BaseRequest: common.NewBaseRequest(),
Notification: dto,
}
}
137 changes: 137 additions & 0 deletions v2/dtos/requests/notification_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//
// Copyright (C) 2021 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package requests

import (
"encoding/json"
"testing"

"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos/common"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models"

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

var (
testNotificationCategory = "category"
testNotificationLabels = []string{"label1", "label2"}
testNotificationContent = "content"
testNotificationContentType = "text/plain"
testNotificationDescription = "description"
testNotificationSender = "sender"
testNotificationSeverity = models.Normal
testNotificationStatus = models.New
)

var testAddNotification = dtos.Notification{
Versionable: common.NewVersionable(),
Category: testNotificationCategory,
Labels: testNotificationLabels,
Content: testNotificationContent,
ContentType: testNotificationContentType,
Description: testNotificationDescription,
Sender: testNotificationSender,
Severity: testNotificationSeverity,
Status: testNotificationStatus,
}

func TestAddNotification_Validate(t *testing.T) {

noReqId := NewAddNotificationRequest(testAddNotification)
noReqId.RequestId = ""
invalidReqId := NewAddNotificationRequest(testAddNotification)
invalidReqId.RequestId = "abc"

noCategoryAndLabel := NewAddNotificationRequest(testAddNotification)
noCategoryAndLabel.Notification.Category = ""
noCategoryAndLabel.Notification.Labels = nil
categoryNameWithReservedChar := NewAddNotificationRequest(testAddNotification)
categoryNameWithReservedChar.Notification.Category = namesWithReservedChar[0]

noContent := NewAddNotificationRequest(testAddNotification)
noContent.Notification.Content = ""

noSender := NewAddNotificationRequest(testAddNotification)
noSender.Notification.Sender = ""

noSeverity := NewAddNotificationRequest(testAddNotification)
noSeverity.Notification.Severity = ""
invalidSeverity := NewAddNotificationRequest(testAddNotification)
invalidSeverity.Notification.Severity = "foo"

invalidStatus := NewAddNotificationRequest(testAddNotification)
invalidStatus.Notification.Status = "foo"

tests := []struct {
name string
request AddNotificationRequest
expectError bool
}{
{"valid", NewAddNotificationRequest(testAddNotification), false},
{"invalid, request ID is not an UUID", invalidReqId, true},
{"invalid, no category and labels", noCategoryAndLabel, true},
{"invalid, category name containing reserved chars", categoryNameWithReservedChar, true},
{"invalid, no content", noContent, true},
{"invalid, no sender", noSender, true},
{"invalid, no severity", noSeverity, true},
{"invalid, unsupported severity level", invalidSeverity, true},
{"invalid, unsupported status", invalidStatus, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.request.Validate()
assert.Equal(t, tt.expectError, err != nil, "Unexpected AddNotificationRequest validation result.", err)
})
}
}

func TestAddNotification_UnmarshalJSON(t *testing.T) {
addNotificationRequest := NewAddNotificationRequest(testAddNotification)
jsonData, _ := json.Marshal(addNotificationRequest)
tests := []struct {
name string
expected AddNotificationRequest
data []byte
wantErr bool
}{
{"unmarshal AddNotificationRequest with success", addNotificationRequest, jsonData, false},
{"unmarshal invalid AddNotificationRequest, empty data", AddNotificationRequest{}, []byte{}, true},
{"unmarshal invalid AddNotificationRequest, string data", AddNotificationRequest{}, []byte("Invalid AddNotificationRequest"), true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var result AddNotificationRequest
err := result.UnmarshalJSON(tt.data)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expected, result, "Unmarshal did not result in expected AddNotificationRequest.")
}
})
}
}

func TestAddNotificationReqToNotificationModels(t *testing.T) {
addNotificationRequest := NewAddNotificationRequest(testAddNotification)
requests := []AddNotificationRequest{addNotificationRequest}
expectedNotificationModel := []models.Notification{
{
Category: testNotificationCategory,
Content: testNotificationContent,
ContentType: testNotificationContentType,
Description: testNotificationDescription,
Labels: testNotificationLabels,
Sender: testNotificationSender,
Severity: models.NotificationSeverity(testNotificationSeverity),
Status: models.NotificationStatus(testNotificationStatus),
},
}
resultModels := AddNotificationReqToNotificationModels(requests)
assert.Equal(t, expectedNotificationModel, resultModels, "AddNotificationReqToNotificationModels did not result in expected Notification model.")
}
2 changes: 1 addition & 1 deletion v2/dtos/requests/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func ReplaceSubscriptionModelFieldsWithDTO(s *models.Subscription, patch dtos.Up
s.Channels = dtos.ToChannelModels(patch.Channels)
}
if patch.Categories != nil {
s.Categories = dtos.ToCategoryModels(patch.Categories)
s.Categories = patch.Categories
}
if patch.Labels != nil {
s.Labels = patch.Labels
Expand Down
Loading

0 comments on commit caf1e88

Please sign in to comment.