diff --git a/LICENSE.md b/LICENSE.md index 1e68e6d..742227a 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ The MIT License (MIT) -Copyright (c) 2019 -2020 Office for National Statistics (https://www.ons.gov.uk) +Copyright (c) 2019-2022 Office for National Statistics (https://www.ons.gov.uk) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/go.mod b/go.mod index 2fd4438..9c5b997 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,21 @@ module github.com/ONSdigital/dp-net/v2 go 1.17 require ( - github.com/ONSdigital/dp-api-clients-go/v2 v2.92.2 - github.com/ONSdigital/dp-net v1.2.0 - github.com/ONSdigital/log.go/v2 v2.1.0 - github.com/aws/aws-sdk-go v1.42.47 + github.com/ONSdigital/dp-api-clients-go/v2 v2.117.0 + github.com/ONSdigital/dp-net v1.4.1 + github.com/ONSdigital/log.go/v2 v2.2.0 + github.com/aws/aws-sdk-go v1.43.38 github.com/gorilla/mux v1.8.0 github.com/justinas/alice v1.2.0 github.com/pkg/errors v0.9.1 github.com/smartystreets/goconvey v1.7.2 github.com/stretchr/testify v1.7.0 - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd + golang.org/x/net v0.0.0-20220412020605-290c469a71a5 ) require ( github.com/ONSdigital/dp-api-clients-go v1.43.0 // indirect - github.com/ONSdigital/dp-healthcheck v1.2.3 // indirect + github.com/ONSdigital/dp-healthcheck v1.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/gopherjs/gopherjs v0.0.0-20220104163920-15ed2e8cf2bd // indirect @@ -29,7 +29,7 @@ require ( github.com/mattn/go-isatty v0.0.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/smartystreets/assertions v1.2.1 // indirect - golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a // indirect + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/go.sum b/go.sum index 55a7515..156ba0a 100644 --- a/go.sum +++ b/go.sum @@ -44,12 +44,14 @@ github.com/ONSdigital/dp-api-clients-go v1.34.3/go.mod h1:kX+YKuoLYLfkeLHMvQKRRy github.com/ONSdigital/dp-api-clients-go v1.41.1/go.mod h1:Ga1+ANjviu21NFJI9wp5NctJIdB4TJLDGbpQFl2V8Wc= github.com/ONSdigital/dp-api-clients-go v1.43.0 h1:0982P/YxnYXvba1RhEcFmwF3xywC4eXokWQ8YH3Mm24= github.com/ONSdigital/dp-api-clients-go v1.43.0/go.mod h1:V5MfINik+o3OAF985UXUoMjXIfrZe3JKYa5AhZn5jts= -github.com/ONSdigital/dp-api-clients-go/v2 v2.92.2 h1:KlxSi4kRz9nO5j5OjCgnjQsyoRAky5WOmMKQPbNPHA8= github.com/ONSdigital/dp-api-clients-go/v2 v2.92.2/go.mod h1:P3GYyqqXnbp4RjWhz0oYpUFjHPT6Ca4fEoh4huNkxHU= +github.com/ONSdigital/dp-api-clients-go/v2 v2.117.0 h1:F8VFk7f/cToCOfm3md6ZNMzNb1ddfoc94+24td0AjIE= +github.com/ONSdigital/dp-api-clients-go/v2 v2.117.0/go.mod h1:tHRq65gA340CYpkHIRMrrxiLS8IlAl7nTeFr07ukJgo= github.com/ONSdigital/dp-healthcheck v1.0.5/go.mod h1:2wbVAUHMl9+4tWhUlxYUuA1dnf2+NrwzC+So5f5BMLk= github.com/ONSdigital/dp-healthcheck v1.1.0/go.mod h1:vZwyjMJiCHjp/sJ2R1ZEqzZT0rJ0+uHVGwxqdP4J5vg= -github.com/ONSdigital/dp-healthcheck v1.2.3 h1:8/qTe0TjXouQWW0jgrtDGMFl+fUWigfyntL+q96GUSY= github.com/ONSdigital/dp-healthcheck v1.2.3/go.mod h1:XUhXoDIWPCdletDtpDOoXhmDFcc9b/kbedx96jN75aI= +github.com/ONSdigital/dp-healthcheck v1.3.0 h1:WQLajH8Ne47fHcz3of9dZKCvqeBmjw+Kyx4bHzpzZLk= +github.com/ONSdigital/dp-healthcheck v1.3.0/go.mod h1:XUhXoDIWPCdletDtpDOoXhmDFcc9b/kbedx96jN75aI= github.com/ONSdigital/dp-mocking v0.0.0-20190905163309-fee2702ad1b9 h1:+WXVfTDyWXY1DQRDFSmt1b/ORKk5c7jGiPu7NoeaM/0= github.com/ONSdigital/dp-mocking v0.0.0-20190905163309-fee2702ad1b9/go.mod h1:BcIRgitUju//qgNePRBmNjATarTtynAgc0yV29VpLEk= github.com/ONSdigital/dp-net v1.0.5-0.20200805082802-e518bc287596/go.mod h1:wDVhk2pYosQ1q6PXxuFIRYhYk2XX5+1CeRRnXpSczPY= @@ -57,8 +59,11 @@ github.com/ONSdigital/dp-net v1.0.5-0.20200805145012-9227a11caddb/go.mod h1:MrSZ github.com/ONSdigital/dp-net v1.0.5-0.20200805150805-cac050646ab5/go.mod h1:de3LB9tedE0tObBwa12dUOt5rvTW4qQkF5rXtt4b6CE= github.com/ONSdigital/dp-net v1.0.7/go.mod h1:1QFzx32FwPKD2lgZI6MtcsUXritsBdJihlzIWDrQ/gc= github.com/ONSdigital/dp-net v1.0.12/go.mod h1:2lvIKOlD4T3BjWQwjHhBUO2UNWDk82u/+mHRn0R3C9A= -github.com/ONSdigital/dp-net v1.2.0 h1:gP9pBt/J8gktYeKsb7hq6uOC2xx1tfvTorUBNXp6pX0= github.com/ONSdigital/dp-net v1.2.0/go.mod h1:NinlaqcsPbIR+X7j5PXCl3UI5G2zCL041SDF6WIiiO4= +github.com/ONSdigital/dp-net v1.4.1 h1:hfhtQ2l7pGtC2nBze7YtFzvkVEMCYprHgojNYVPL134= +github.com/ONSdigital/dp-net v1.4.1/go.mod h1:VK8dah+G0TeVO/Os/w17Rk4WM6hIGmdUXrm8fBWyC+g= +github.com/ONSdigital/dp-net/v2 v2.0.0/go.mod h1:Pv/35rM5tCLYdVdIZ5KoGu2EUXv/87fWTptlVTlS5MY= +github.com/ONSdigital/dp-net/v2 v2.2.0/go.mod h1:Pv/35rM5tCLYdVdIZ5KoGu2EUXv/87fWTptlVTlS5MY= github.com/ONSdigital/go-ns v0.0.0-20191104121206-f144c4ec2e58/go.mod h1:iWos35il+NjbvDEqwtB736pyHru0MPFE/LqcwkV1wDc= github.com/ONSdigital/log.go v1.0.0/go.mod h1:UnGu9Q14gNC+kz0DOkdnLYGoqugCvnokHBRBxFRpVoQ= github.com/ONSdigital/log.go v1.0.1-0.20200805084515-ee61165ea36a/go.mod h1:dDnQATFXCBOknvj6ZQuKfmDhbOWf3e8mtV+dPEfWJqs= @@ -69,14 +74,17 @@ github.com/ONSdigital/log.go v1.1.0/go.mod h1:0hOVuYR3bDUI30VRo48d5KHfJIoe+spuPX github.com/ONSdigital/log.go/v2 v2.0.0/go.mod h1:PR7vXrv9dZKUc7SI/0toxBbStk84snmybBnWpe+xY2o= github.com/ONSdigital/log.go/v2 v2.0.5/go.mod h1:PR7vXrv9dZKUc7SI/0toxBbStk84snmybBnWpe+xY2o= github.com/ONSdigital/log.go/v2 v2.0.9/go.mod h1:VyTDkL82FtiAkaNFaT+bURBhLbP7NsIx4rkVbdpiuEg= -github.com/ONSdigital/log.go/v2 v2.1.0 h1:nEPqMYyKQlbY8VPgMbnYpNpomUOgLrwEtMZZbuXC/5c= github.com/ONSdigital/log.go/v2 v2.1.0/go.mod h1:9w+SkChyhtIK7XCha+cq6bq2DpTaK16Q4LofgoEGMSk= +github.com/ONSdigital/log.go/v2 v2.2.0 h1:Rkv6Gbfa3Jzy5AfPWkUDEnpFNhzYyaGQ6f7YUdBKnHs= +github.com/ONSdigital/log.go/v2 v2.2.0/go.mod h1:i0eFlPDlF1fI4k0/SvXhQIkyQxs676EySpYPj3rQy+I= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.42.47 h1:Faabrbp+bOBiZjHje7Hbhvni212aQYQIXZMruwkgmmA= +github.com/aws/aws-sdk-go v1.38.15/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.42.47/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= +github.com/aws/aws-sdk-go v1.43.38 h1:TDRjsUIsx2aeSuKkyzbwgltIRTbIKH6YCZbZ27JYhPk= +github.com/aws/aws-sdk-go v1.43.38/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -404,8 +412,9 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -480,8 +489,9 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a h1:ppl5mZgokTT8uPkmYOyEUmPTr3ypaKkg5eFOGrAmxxE= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/handlers/response/etag.go b/handlers/response/etag.go index 44181fa..acc19d0 100644 --- a/handlers/response/etag.go +++ b/handlers/response/etag.go @@ -15,6 +15,8 @@ const ( // A strong or weak eTag can be generated. Please note that ETags are surrounded by double quotes. // // Example: ETag = `"24decf55038de874bc6fa9cf0930adc219f15db1"` +// +// The definition of ETag is explained in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag func GenerateETag(body []byte, weak bool) (etag string) { hash := sha1.Sum(body) diff --git a/request/patch.go b/request/patch.go index eca3247..2051fdc 100644 --- a/request/patch.go +++ b/request/patch.go @@ -22,17 +22,15 @@ const ( var validOps = []string{"add", "remove", "replace", "move", "copy", "test"} -var patchOpsMap = map[string]PatchOp{ - "add": OpAdd, - "remove": OpRemove, - "replace": OpReplace, - "move": OpMove, - "copy": OpCopy, - "test": OpTest, -} +// ErrInvalidOp generates an error when a patch contains a wrong 'op' +func ErrInvalidOp(supportedOps []PatchOp) error { + if len(supportedOps) < 1 { + return fmt.Errorf("patch operation is invalid as no patch operations are supported") + } -// ErrInvalidOp is an error returned when a patch contains a wrong 'op' -var ErrInvalidOp = fmt.Errorf("operation is missing or not valid. Please, provide one of the following: %v", validOps) + validSupportedOps := getPatchOpsStringSlice(supportedOps) + return fmt.Errorf("patch operation is missing or invalid. Please, provide one of the following: %v", validSupportedOps) +} // ErrMissingMember generates an error for a missing member func ErrMissingMember(members []string) error { @@ -41,10 +39,7 @@ func ErrMissingMember(members []string) error { // ErrUnsupportedOp generates an error for unsupported ops func ErrUnsupportedOp(op string, supportedOps []PatchOp) error { - supported := []string{} - for _, op := range supportedOps { - supported = append(supported, op.String()) - } + supported := getPatchOpsStringSlice(supportedOps) return fmt.Errorf("op '%s' not supported. Supported op(s): %v", op, supported) } @@ -52,6 +47,14 @@ func (o PatchOp) String() string { return validOps[o] } +func getPatchOpsStringSlice(ops []PatchOp) []string { + opsStringSlice := []string{} + for _, op := range ops { + opsStringSlice = append(opsStringSlice, op.String()) + } + return opsStringSlice +} + // Patch models an HTTP patch operation request, according to RFC 6902 type Patch struct { Op string `json:"op"` @@ -63,17 +66,25 @@ type Patch struct { // GetPatches gets the patches from the request body and returns it in the form of []Patch. // An error will be returned if request body cannot be read, unmarshalling the requets body is unsuccessful, // no patches are provided in the request or any of the provided patches are invalid -func GetPatches(requestBody io.ReadCloser) ([]Patch, error) { +func GetPatches(requestBody io.ReadCloser, supportedOps []PatchOp) ([]Patch, error) { patches := []Patch{} + if len(supportedOps) < 1 { + return []Patch{}, fmt.Errorf("empty list of support patch operations given") + } + bytes, err := ioutil.ReadAll(requestBody) if err != nil { - return []Patch{}, fmt.Errorf("failed to read and get patch request body - error: %v", err) + return []Patch{}, fmt.Errorf("failed to read and get patch request body") + } + + if len(bytes) == 0 { + return []Patch{}, fmt.Errorf("empty request body given") } err = json.Unmarshal(bytes, &patches) if err != nil { - return []Patch{}, fmt.Errorf("failed to unmarshal patch request body - error: %v", err) + return []Patch{}, fmt.Errorf("failed to unmarshal patch request body") } if len(patches) < 1 { @@ -81,15 +92,15 @@ func GetPatches(requestBody io.ReadCloser) ([]Patch, error) { } for _, patch := range patches { - if err := patch.Validate(patchOpsMap[patch.Op]); err != nil { - return []Patch{}, fmt.Errorf("failed to validate patch - error: %v", err) + if err := patch.Validate(supportedOps); err != nil { + return []Patch{}, err } } return patches, nil } // Validate checks that the provided operation is correct and the expected members are provided -func (p *Patch) Validate(supportedOps ...PatchOp) error { +func (p *Patch) Validate(supportedOps []PatchOp) error { missing := []string{} switch p.Op { case OpAdd.String(), OpReplace.String(), OpTest.String(): @@ -111,7 +122,7 @@ func (p *Patch) Validate(supportedOps ...PatchOp) error { missing = append(missing, "from") } default: - return ErrInvalidOp + return ErrInvalidOp(supportedOps) } if !p.isOpSupported(supportedOps) { diff --git a/request/patch_test.go b/request/patch_test.go index 2968fd8..2f124d2 100644 --- a/request/patch_test.go +++ b/request/patch_test.go @@ -11,7 +11,7 @@ import ( ) func TestGetPatches(t *testing.T) { - Convey("Given a patch request", t, func() { + Convey("Given a patch request body", t, func() { body := strings.NewReader(`[ { "op": "test", "path": "/a/b/c", "value": "foo" }, { "op": "remove", "path": "/a/b/c" }, @@ -22,97 +22,168 @@ func TestGetPatches(t *testing.T) { ]`) req := httptest.NewRequest(http.MethodPatch, "http://localhost:21800/jobs/12345", body) - Convey("When GetPatches is called", func() { - patches, err := GetPatches(req.Body) - - Convey("Then all the patches are in the form of []Patch", func() { - So(err, ShouldBeNil) - So(patches, ShouldHaveLength, 6) - - So(patches[0].Op, ShouldEqual, "test") - So(patches[0].Path, ShouldEqual, "/a/b/c") - So(patches[0].From, ShouldBeEmpty) - So(patches[0].Value, ShouldEqual, "foo") - - So(patches[1].Op, ShouldEqual, "remove") - So(patches[1].Path, ShouldEqual, "/a/b/c") - So(patches[1].From, ShouldBeEmpty) - So(patches[1].Value, ShouldBeEmpty) - - So(patches[2].Op, ShouldEqual, "add") - So(patches[2].Path, ShouldEqual, "/a/b/c") - So(patches[2].From, ShouldBeEmpty) - So(patches[2].Value, ShouldResemble, []interface{}{"foo", "bar"}) - - So(patches[3].Op, ShouldEqual, "replace") - So(patches[3].Path, ShouldEqual, "/a/b/c") - So(patches[3].From, ShouldBeEmpty) - So(patches[3].Value, ShouldEqual, 42) - - So(patches[4].Op, ShouldEqual, "move") - So(patches[4].Path, ShouldEqual, "/a/b/d") - So(patches[4].From, ShouldEqual, "/a/b/c") - So(patches[4].Value, ShouldBeEmpty) - - So(patches[5].Op, ShouldEqual, "copy") - So(patches[5].Path, ShouldEqual, "/a/b/e") - So(patches[5].From, ShouldEqual, "/a/b/d") - So(patches[5].Value, ShouldBeEmpty) + Convey("And all patch operations are supported", func() { + supportedOps := []PatchOp{ + OpTest, + OpRemove, + OpAdd, + OpReplace, + OpMove, + OpCopy, + } + + Convey("When GetPatches is called", func() { + patches, err := GetPatches(req.Body, supportedOps) + + Convey("Then all the patches are in the form of []Patch", func() { + So(err, ShouldBeNil) + So(patches, ShouldHaveLength, 6) + + So(patches[0].Op, ShouldEqual, "test") + So(patches[0].Path, ShouldEqual, "/a/b/c") + So(patches[0].From, ShouldBeEmpty) + So(patches[0].Value, ShouldEqual, "foo") + + So(patches[1].Op, ShouldEqual, "remove") + So(patches[1].Path, ShouldEqual, "/a/b/c") + So(patches[1].From, ShouldBeEmpty) + So(patches[1].Value, ShouldBeEmpty) + + So(patches[2].Op, ShouldEqual, "add") + So(patches[2].Path, ShouldEqual, "/a/b/c") + So(patches[2].From, ShouldBeEmpty) + So(patches[2].Value, ShouldResemble, []interface{}{"foo", "bar"}) + + So(patches[3].Op, ShouldEqual, "replace") + So(patches[3].Path, ShouldEqual, "/a/b/c") + So(patches[3].From, ShouldBeEmpty) + So(patches[3].Value, ShouldEqual, 42) + + So(patches[4].Op, ShouldEqual, "move") + So(patches[4].Path, ShouldEqual, "/a/b/d") + So(patches[4].From, ShouldEqual, "/a/b/c") + So(patches[4].Value, ShouldBeEmpty) + + So(patches[5].Op, ShouldEqual, "copy") + So(patches[5].Path, ShouldEqual, "/a/b/e") + So(patches[5].From, ShouldEqual, "/a/b/d") + So(patches[5].Value, ShouldBeEmpty) + }) }) }) }) - Convey("Given a patch request with unknown patch operation given in request body", t, func() { - body := strings.NewReader(`[ - { "op": "invalid", "path": "/a/b/c", "value": "foo" } - ]`) + Convey("Given empty list of supported patch operations", t, func() { + emptySupportedOps := []PatchOp{} + + Convey("And valid patch request body", func() { + body := strings.NewReader(`[ + { "op": "test", "path": "/a/b/c", "value": "foo" } + ]`) + req := httptest.NewRequest(http.MethodPatch, "http://localhost:21800/jobs/12345", body) + + Convey("When GetPatches is called", func() { + patches, err := GetPatches(req.Body, emptySupportedOps) + + Convey("Then an error should be returned ", func() { + So(err, ShouldNotBeNil) + So(err, ShouldResemble, fmt.Errorf("empty list of support patch operations given")) + + Convey("And an empty patch array should be returned", func() { + So(patches, ShouldBeEmpty) + }) + }) + }) + }) + }) + + Convey("Given an empty request body", t, func() { + emptyBody := strings.NewReader("") + req := httptest.NewRequest(http.MethodPatch, "http://localhost:21800/jobs/12345", emptyBody) + + Convey("And valid supported patch operations given", func() { + supportedOps := []PatchOp{OpAdd} + + Convey("When GetPatches is called", func() { + patches, err := GetPatches(req.Body, supportedOps) + + Convey("Then an error should be returned ", func() { + So(err, ShouldNotBeNil) + So(err, ShouldResemble, fmt.Errorf("empty request body given")) + + Convey("And an empty patch array should be returned", func() { + So(patches, ShouldBeEmpty) + }) + }) + }) + }) + }) + + Convey("Given a patch request with invalid patch request body", t, func() { + body := strings.NewReader(`{}`) req := httptest.NewRequest(http.MethodPatch, "http://localhost:21800/jobs/12345", body) - Convey("When GetPatches is called", func() { - patches, err := GetPatches(req.Body) + Convey("And valid supported patch operations given", func() { + supportedOps := []PatchOp{OpAdd} - Convey("Then an error should be returned ", func() { - So(err, ShouldNotBeNil) - So(err, ShouldResemble, fmt.Errorf("failed to validate patch - error: operation is missing or not valid. Please, provide one of the following: %v", validOps)) + Convey("When GetPatches is called", func() { + patches, err := GetPatches(req.Body, supportedOps) - Convey("And an empty patch array should be returned", func() { - So(patches, ShouldBeEmpty) + Convey("Then an error should be returned ", func() { + So(err, ShouldNotBeNil) + So(err, ShouldResemble, fmt.Errorf("failed to unmarshal patch request body")) + + Convey("And an empty patch array should be returned", func() { + So(patches, ShouldBeEmpty) + }) }) }) }) }) - Convey("Given a patch request with an array of unknown patch operation given in request body", t, func() { + Convey("Given a patch request with no patch operation given in request body", t, func() { body := strings.NewReader(`[]`) req := httptest.NewRequest(http.MethodPatch, "http://localhost:21800/jobs/12345", body) - Convey("When GetPatches is called", func() { - patches, err := GetPatches(req.Body) + Convey("And valid supported patch operations given", func() { + supportedOps := []PatchOp{OpAdd} + + Convey("When GetPatches is called", func() { + patches, err := GetPatches(req.Body, supportedOps) - Convey("Then an error should be returned ", func() { - So(err, ShouldNotBeNil) - So(err, ShouldResemble, fmt.Errorf("no patches given in request body")) + Convey("Then an error should be returned ", func() { + So(err, ShouldNotBeNil) + So(err, ShouldResemble, fmt.Errorf("no patches given in request body")) - Convey("And an empty patch array should be returned", func() { - So(patches, ShouldBeEmpty) + Convey("And an empty patch array should be returned", func() { + So(patches, ShouldBeEmpty) + }) }) }) }) }) Convey("Given a patch request with unknown patch operation given in request body", t, func() { - body := strings.NewReader(`{}`) + body := strings.NewReader(`[ + { "op": "invalid", "path": "/a/b/c", "value": "foo" } + ]`) req := httptest.NewRequest(http.MethodPatch, "http://localhost:21800/jobs/12345", body) - Convey("When GetPatches is called", func() { - patches, err := GetPatches(req.Body) + Convey("And valid supported patch operations given", func() { + supportedOps := []PatchOp{OpAdd} + + Convey("When GetPatches is called", func() { + patches, err := GetPatches(req.Body, supportedOps) + + Convey("Then an error should be returned ", func() { + So(err, ShouldNotBeNil) - Convey("Then an error should be returned ", func() { - So(err, ShouldNotBeNil) - So(err, ShouldResemble, fmt.Errorf("failed to unmarshal patch request body - error: json: cannot unmarshal object into Go value of type []request.Patch")) + supportedOpsStringSlice := getPatchOpsStringSlice(supportedOps) + So(err, ShouldResemble, fmt.Errorf("patch operation is missing or invalid. Please, provide one of the following: %v", supportedOpsStringSlice)) - Convey("And an empty patch array should be returned", func() { - So(patches, ShouldBeEmpty) + Convey("And an empty patch array should be returned", func() { + So(patches, ShouldBeEmpty) + }) }) }) }) @@ -120,14 +191,14 @@ func TestGetPatches(t *testing.T) { } func TestValidate(t *testing.T) { - Convey("Validating a valid patch with a supported op and array of strings value is successful", t, func() { patch := Patch{ Op: "add", Path: "/a/b/c", Value: []string{"foo"}, } - So(patch.Validate(OpAdd), ShouldBeNil) + supportedOps := []PatchOp{OpAdd} + So(patch.Validate(supportedOps), ShouldBeNil) }) Convey("Validating a valid patch with a supported op and float64 value is successful", t, func() { @@ -136,7 +207,8 @@ func TestValidate(t *testing.T) { Path: "/a/b/c", Value: float64(123.321), } - So(patch.Validate(OpAdd), ShouldBeNil) + supportedOps := []PatchOp{OpAdd} + So(patch.Validate(supportedOps), ShouldBeNil) }) Convey("Validating a patch struct with an invalid op fails with the expected error", t, func() { @@ -145,7 +217,8 @@ func TestValidate(t *testing.T) { Path: "/a/b/c", Value: []string{"foo"}, } - So(patch.Validate(), ShouldResemble, ErrInvalidOp) + emptySupportedOps := []PatchOp{} + So(patch.Validate(emptySupportedOps), ShouldResemble, ErrInvalidOp(emptySupportedOps)) }) Convey("Validating a valid patch with an unsupported op fails with the expected error", t, func() { @@ -154,38 +227,39 @@ func TestValidate(t *testing.T) { Path: "/a/b/c", Value: []string{"foo"}, } - So(patch.Validate(OpRemove), ShouldResemble, ErrUnsupportedOp("add", []PatchOp{OpRemove})) + supportedOps := []PatchOp{OpRemove} + So(patch.Validate(supportedOps), ShouldResemble, ErrUnsupportedOp("add", []PatchOp{OpRemove})) }) Convey("Validating a patch struct with missing members for an operation results in the expected error being returned", t, func() { + supportedOps := []PatchOp{OpAdd, OpReplace, OpTest, OpRemove, OpMove, OpCopy} patch := Patch{ Op: "add", Path: "/a/b/c", } - So(patch.Validate(OpAdd), ShouldResemble, ErrMissingMember([]string{"value"})) + So(patch.Validate(supportedOps), ShouldResemble, ErrMissingMember([]string{"value"})) patch = Patch{ Op: "replace", Value: []string{"foo"}, } - So(patch.Validate(OpReplace), ShouldResemble, ErrMissingMember([]string{"path"})) + So(patch.Validate(supportedOps), ShouldResemble, ErrMissingMember([]string{"path"})) patch = Patch{ Op: "test", } - So(patch.Validate(OpTest), ShouldResemble, ErrMissingMember([]string{"path", "value"})) + So(patch.Validate(supportedOps), ShouldResemble, ErrMissingMember([]string{"path", "value"})) patch = Patch{ Op: "remove", } - So(patch.Validate(OpRemove), ShouldResemble, ErrMissingMember([]string{"path"})) + So(patch.Validate(supportedOps), ShouldResemble, ErrMissingMember([]string{"path"})) patch = Patch{ Op: "move", Path: "/a/b/c", } - So(patch.Validate(OpMove), ShouldResemble, ErrMissingMember([]string{"from"})) + So(patch.Validate(supportedOps), ShouldResemble, ErrMissingMember([]string{"from"})) patch = Patch{ Op: "copy", From: "/c/b/a", } - So(patch.Validate(OpCopy), ShouldResemble, ErrMissingMember([]string{"path"})) + So(patch.Validate(supportedOps), ShouldResemble, ErrMissingMember([]string{"path"})) }) - }