Skip to content

Commit

Permalink
added custom duration for webhook registration unmarshaling (#103)
Browse files Browse the repository at this point in the history
* added custom duration for webhook registration unmarshaling

* added tests, fixed error

* updated changelog, prep for release
  • Loading branch information
kristinapathak authored Apr 28, 2022
1 parent d3db9b4 commit e4d9474
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 11 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

## [v0.3.9]
- Updated wrp decoding for webhook registration to accept an int in seconds or a string of the form "5m". [#103](https://github.com/xmidt-org/ancla/pull/103)

## [v0.3.8]
- Added measures and providemetrics func to metrics.go. [#98](https://github.com/xmidt-org/ancla/pull/98)
Expand Down Expand Up @@ -98,7 +100,8 @@ internalWebhooks. [#80](https://github.com/xmidt-org/ancla/pull/80)
## [v0.1.0]
- Initial release

[Unreleased]: https://github.com/xmidt-org/ancla/compare/v0.3.8...HEAD
[Unreleased]: https://github.com/xmidt-org/ancla/compare/v0.3.9...HEAD
[v0.3.9]: https://github.com/xmidt-org/ancla/compare/v0.3.8...v0.3.9
[v0.3.8]: https://github.com/xmidt-org/ancla/compare/v0.3.7...v0.3.8
[v0.3.7]: https://github.com/xmidt-org/ancla/compare/v0.3.6...v0.3.7
[v0.3.6]: https://github.com/xmidt-org/ancla/compare/v0.3.5...v0.3.6
Expand Down
74 changes: 74 additions & 0 deletions customDuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright 2022 Comcast Cable Communications Management, LLC
*
* 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 ancla

import (
"bytes"
"strconv"
"strings"
"time"
)

type InvalidDurationError struct {
Value string
}

func (ide *InvalidDurationError) Error() string {
var o strings.Builder
o.WriteString("duration must be of type int or string (ex:'5m'); Invalid value: ")
o.WriteString(ide.Value)
return o.String()
}

type CustomDuration time.Duration

func (cd CustomDuration) String() string {
return time.Duration(cd).String()
}

func (cd CustomDuration) MarshalJSON() ([]byte, error) {
d := bytes.NewBuffer(nil)
d.WriteByte('"')
d.WriteString(cd.String())
d.WriteByte('"')
return d.Bytes(), nil
}

func (cd *CustomDuration) UnmarshalJSON(b []byte) (err error) {
if b[0] == '"' {
var d time.Duration
d, err = time.ParseDuration(string(b[1 : len(b)-1]))
if err == nil {
*cd = CustomDuration(d)
return
}
}

var d int64
d, err = strconv.ParseInt(string(b), 10, 64)
if err == nil {
*cd = CustomDuration(time.Duration(d) * time.Second)
return
}

err = &InvalidDurationError{
Value: string(b),
}

return
}
108 changes: 108 additions & 0 deletions customDuration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright 2022 Comcast Cable Communications Management, LLC
*
* 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 ancla

import (
"encoding/json"
"testing"
"time"

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

func TestUnmarshalJSON(t *testing.T) {
type test struct {
Duration CustomDuration
}
tests := []struct {
description string
input []byte
expectedDuration CustomDuration
errExpected bool
}{
{
description: "Int success",
input: []byte(`{"duration":50}`),
expectedDuration: CustomDuration(50 * time.Second),
},
{
description: "String success",
input: []byte(`{"duration":"5m"}`),
expectedDuration: CustomDuration(5 * time.Minute),
},
{
description: "String failure",
input: []byte(`{"duration":"2r"}`),
errExpected: true,
},
{
description: "Object failure",
input: []byte(`{"duration":{"key":"val"}}`),
errExpected: true,
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
cd := test{}
err := json.Unmarshal(tc.input, &cd)
assert.Equal(tc.expectedDuration, cd.Duration)
if !tc.errExpected {
assert.NoError(err)
return
}
assert.Error(err)
})
}
}

func TestMarshalJSON(t *testing.T) {
type test struct {
Duration CustomDuration
}
tests := []struct {
description string
input test
expectedOutput []byte
errExpected bool
}{
{
description: "Int success",
input: test{Duration: CustomDuration(50 * time.Second)},
expectedOutput: []byte(`{"Duration":"50s"}`),
},
{
description: "String success",
input: test{Duration: CustomDuration(5 * time.Minute)},
expectedOutput: []byte(`{"Duration":"5m0s"}`),
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
output, err := json.Marshal(tc.input)
assert.Equal(tc.expectedOutput, output)
if !tc.errExpected {
assert.NoError(err)
return
}
assert.Error(err)
})
}

}
7 changes: 4 additions & 3 deletions transport.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2021 Comcast Cable Communications Management, LLC
* Copyright 2022 Comcast Cable Communications Management, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -108,9 +108,9 @@ func addWebhookRequestDecoder(config transportConfig) kithttp.DecodeRequestFunc
if err != nil {
return nil, err
}
var webhook Webhook
var wr WebhookRegistration

err = json.Unmarshal(requestPayload, &webhook)
err = json.Unmarshal(requestPayload, &wr)
if err != nil {
var e *json.UnmarshalTypeError
if errors.As(err, &e) {
Expand All @@ -119,6 +119,7 @@ func addWebhookRequestDecoder(config transportConfig) kithttp.DecodeRequestFunc
return nil, &erraux.Error{Err: fmt.Errorf("%w: %v", errFailedWebhookUnmarshal, err), Code: http.StatusBadRequest}
}

webhook := wr.ToWebhook()
err = config.v.Validate(webhook)
if err != nil {
return nil, &erraux.Error{Err: err, Message: "failed webhook validation", Code: http.StatusBadRequest}
Expand Down
106 changes: 101 additions & 5 deletions transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ func TestAddWebhookRequestDecoder(t *testing.T) {
Validator: Validators{},
Auth: "jwt",
},
{
Description: "Normal happy path using Duration",
InputPayload: addWebhookDecoderDurationInput(),
ExpectedDecodedRequest: addWebhookDecoderDurationOutput(true),
Validator: Validators{},
Auth: "jwt",
},
{
Description: "No validator provided",
InputPayload: addWebhookDecoderInput(),
Expand Down Expand Up @@ -248,8 +255,16 @@ func TestAddWebhookRequestDecoder(t *testing.T) {
Auth: "jwt",
},
{
Description: "Failed to JSON Unmarshal",
InputPayload: addWebhookDecoderUnmarshalingErrorInput(),
Description: "Failed to JSON Unmarshal Type Error",
InputPayload: addWebhookDecoderUnmarshalingErrorInput(false),
ExpectedErr: errFailedWebhookUnmarshal,
Validator: Validators{},
ExpectedStatusCode: 400,
Auth: "jwt",
},
{
Description: "Failed to JSON Unmarshal Invalid Duration Error",
InputPayload: addWebhookDecoderUnmarshalingErrorInput(true),
ExpectedErr: errFailedWebhookUnmarshal,
Validator: Validators{},
ExpectedStatusCode: 400,
Expand Down Expand Up @@ -346,7 +361,7 @@ func TestAddWebhookRequestDecoder(t *testing.T) {
}

} else {
assert.Nil(err)
assert.NoError(err)
assert.EqualValues(tc.ExpectedDecodedRequest, decodedRequest)
}

Expand All @@ -373,9 +388,27 @@ func addWebhookDecoderInput() string {
}
`
}

func addWebhookDecoderUnmarshalingErrorInput() string {
func addWebhookDecoderDurationInput() string {
return `
{
"config": {
"url": "http://deliver-here-0.example.net",
"content_type": "application/json",
"secret": "superSecretXYZ"
},
"events": ["online"],
"matcher": {
"device_id": ["mac:aabbccddee.*"]
},
"failure_url": "http://contact-here-when-fails.example.net",
"duration": 300
}
`
}

func addWebhookDecoderUnmarshalingErrorInput(duration bool) string {
if duration {
return `
{
"config": {
"url": "http://deliver-here-0.example.net",
Expand All @@ -391,6 +424,23 @@ func addWebhookDecoderUnmarshalingErrorInput() string {
"until": "2021-01-02T15:04:10Z"
}
`
}
return `
{
"config": {
"url": "http://deliver-here-0.example.net",
"content_type": 5,
"secret": "superSecretXYZ"
},
"events": ["online"],
"matcher": {
"device_id": ["mac:aabbccddee.*"]
},
"failure_url": "http://contact-here-when-fails.example.net",
"duration": 0,
"until": "2021-01-02T15:04:10Z"
}
`
}

func addWebhookDecoderOutput(withPIDs bool) *addWebhookRequest {
Expand Down Expand Up @@ -439,6 +489,52 @@ func addWebhookDecoderOutput(withPIDs bool) *addWebhookRequest {
},
}
}
func addWebhookDecoderDurationOutput(withPIDs bool) *addWebhookRequest {
if withPIDs {
return &addWebhookRequest{
owner: "owner-from-auth",
internalWebook: InternalWebhook{
Webhook: Webhook{
Address: "original-requester.example.net:443",
Config: DeliveryConfig{
URL: "http://deliver-here-0.example.net",
ContentType: "application/json",
Secret: "superSecretXYZ",
},
Events: []string{"online"},
Matcher: MetadataMatcherConfig{
DeviceID: []string{"mac:aabbccddee.*"},
},
FailureURL: "http://contact-here-when-fails.example.net",
Duration: 5 * time.Minute,
Until: getRefTime().Add(5 * time.Minute),
},
PartnerIDs: []string{"comcast"},
},
}
}
return &addWebhookRequest{
owner: "owner-from-auth",
internalWebook: InternalWebhook{
Webhook: Webhook{
Address: "original-requester.example.net:443",
Config: DeliveryConfig{
URL: "http://deliver-here-0.example.net",
ContentType: "application/json",
Secret: "superSecretXYZ",
},
Events: []string{"online"},
Matcher: MetadataMatcherConfig{
DeviceID: []string{"mac:aabbccddee.*"},
},
FailureURL: "http://contact-here-when-fails.example.net",
Duration: 5 * time.Minute,
Until: getRefTime().Add(5 * time.Minute),
},
PartnerIDs: []string{},
},
}
}

func encodeGetAllInput() []InternalWebhook {
return []InternalWebhook{
Expand Down
Loading

0 comments on commit e4d9474

Please sign in to comment.