From c11c1bfcc9bbfdbc17ef47c1de39470960624680 Mon Sep 17 00:00:00 2001 From: Leonard Goodell Date: Wed, 20 Jul 2022 13:44:25 -0700 Subject: [PATCH 1/2] feat: Add new SystemEvent DTO closes #751 Signed-off-by: Leonard Goodell --- common/constants.go | 8 ++++ dtos/systemevent.go | 71 +++++++++++++++++++++++++++ dtos/systemevent_test.go | 100 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 dtos/systemevent.go create mode 100644 dtos/systemevent_test.go diff --git a/common/constants.go b/common/constants.go index e9561d68..93235642 100644 --- a/common/constants.go +++ b/common/constants.go @@ -292,3 +292,11 @@ const ( ContentTypeText = "text/plain" ContentTypeXML = "application/xml" ) + +// Constants related to System Events +const ( + DeviceSystemEventType = "device" + DeviceSystemEventActionAdd = "add" + DeviceSystemEventActionUpdate = "update" + DeviceSystemEventActionDelete = "delete" +) diff --git a/dtos/systemevent.go b/dtos/systemevent.go new file mode 100644 index 00000000..b1438cc2 --- /dev/null +++ b/dtos/systemevent.go @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright 2022 Intel Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + *******************************************************************************/ + +package dtos + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/common" +) + +// SystemEvent defines the data for a system event +type SystemEvent struct { + common.Versionable `json:",inline"` + Type string + Action string + Source string + Owner string + Tags map[string]string + Details any + Timestamp int64 +} + +// NewSystemEvent creates a new SystemEvent for the specified data +func NewSystemEvent(eventType, action, source, owner string, tags map[string]string, details any) SystemEvent { + return SystemEvent{ + Versionable: common.NewVersionable(), + Type: eventType, + Action: action, + Source: source, + Owner: owner, + Tags: tags, + Details: details, + Timestamp: time.Now().UnixNano(), + } +} + +// DecodeDetails decodes the details (any type) into the passed in object +func (s *SystemEvent) DecodeDetails(details any) error { + if s.Details == nil { + return errors.New("unable to decode System Event details: Details are nil") + } + + // Must encode the details to JSON since if the target SystemEvent was decoded from JSON the details are + // captured in a map[string]interface{}. + data, err := json.Marshal(s.Details) + if err != nil { + return fmt.Errorf("unable to encode System Event details to JSON: %s", err.Error()) + } + + err = json.Unmarshal(data, details) + if err != nil { + return fmt.Errorf("unable to decode System Event details from JSON: %s", err.Error()) + } + + return nil +} diff --git a/dtos/systemevent_test.go b/dtos/systemevent_test.go new file mode 100644 index 00000000..e1d59a5a --- /dev/null +++ b/dtos/systemevent_test.go @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright 2022 Intel Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + *******************************************************************************/ + +package dtos + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/edgexfoundry/go-mod-core-contracts/v2/common" +) + +var expectedApiVersion = common.ApiVersion +var expectedType = common.DeviceSystemEventType +var expectedAction = common.DeviceSystemEventActionAdd +var expectedSoonerTimestamp = time.Now().UnixNano() +var expectedSource = "core-metadata" +var expectedOwner = "device-onvif-camera" +var expectedTags = map[string]string{"device-profile": "onvif-camera"} +var expectedDetails = Device{ + Id: TestUUID, + Name: "My-Camera-Device", + ServiceName: "device-onvif-camera", + ProfileName: "onvif-camera", + Protocols: map[string]ProtocolProperties{ + "Onvif": { + "Address": "192.168.12.123", + "Port": "80", + }, + }, +} + +func TestNewSystemEvent(t *testing.T) { + actual := NewSystemEvent(expectedType, expectedAction, expectedSource, expectedOwner, expectedTags, expectedDetails) + + assert.Equal(t, expectedApiVersion, actual.ApiVersion) + assert.Equal(t, expectedType, actual.Type) + assert.Equal(t, expectedAction, actual.Action) + assert.Equal(t, expectedSource, actual.Source) + assert.Equal(t, expectedOwner, actual.Owner) + assert.Equal(t, expectedTags, actual.Tags) + assert.Equal(t, expectedDetails, actual.Details) + + expectedLaterTimestamp := time.Now().UnixNano() + assert.LessOrEqual(t, expectedSoonerTimestamp, actual.Timestamp) + assert.GreaterOrEqual(t, expectedLaterTimestamp, actual.Timestamp) +} + +func TestDecodeDetails(t *testing.T) { + systemEvent := NewSystemEvent(expectedType, expectedAction, expectedSource, expectedOwner, expectedTags, expectedDetails) + + // Simulate the System Event was received as encoded JSON and has been decoded which results in the Details being + // decoded to a map[string]interface{} since decoder doesn't know the actual type. + data, err := json.Marshal(systemEvent) + require.NoError(t, err) + target := &SystemEvent{} + err = json.Unmarshal(data, target) + require.NoError(t, err) + + actual := &Device{} + err = target.DecodeDetails(actual) + require.NoError(t, err) + assert.Equal(t, expectedDetails, *actual) +} + +func TestDecodeDetailsError(t *testing.T) { + tests := []struct { + Name string + Details any + expectedError string + }{ + {"Nil details", nil, "Details are nil"}, + {"string details", "...", "unable to decode System Event details from JSON"}, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + target := NewSystemEvent(expectedType, expectedAction, expectedSource, expectedOwner, expectedTags, test.Details) + actual := &Device{} + err := target.DecodeDetails(actual) + require.Error(t, err) + require.Contains(t, err.Error(), test.expectedError) + }) + } +} From 05adad6442a327fce51bde0fe648d55687c5342b Mon Sep 17 00:00:00 2001 From: Leonard Goodell Date: Tue, 26 Jul 2022 10:55:33 -0600 Subject: [PATCH 2/2] fix: Add JSON attributes per PR feedback Signed-off-by: Leonard Goodell --- dtos/systemevent.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dtos/systemevent.go b/dtos/systemevent.go index b1438cc2..df0d93c3 100644 --- a/dtos/systemevent.go +++ b/dtos/systemevent.go @@ -26,13 +26,13 @@ import ( // SystemEvent defines the data for a system event type SystemEvent struct { common.Versionable `json:",inline"` - Type string - Action string - Source string - Owner string - Tags map[string]string - Details any - Timestamp int64 + Type string `json:"type"` + Action string `json:"action"` + Source string `json:"source"` + Owner string `json:"owner"` + Tags map[string]string `json:"tags"` + Details any `json:"details"` + Timestamp int64 `json:"timestamp"` } // NewSystemEvent creates a new SystemEvent for the specified data