From 8a8707aae7a66550271e7c77bee67df61e865bdd Mon Sep 17 00:00:00 2001 From: ksaiki Date: Tue, 3 Sep 2024 21:13:16 +0900 Subject: [PATCH] add set subscription attributes v1 --- app/gosns/get_subscription_attributes.go | 74 ++-- app/gosns/gosns.go | 61 +--- app/gosns/gosns_test.go | 69 ---- app/gosns/set_subscription_attributes.go | 66 ++++ app/gosns/set_subscription_attributes_test.go | 317 ++++++++++++++++++ app/models/responses.go | 14 + app/models/sns.go | 15 + app/router/router.go | 4 +- app/router/router_test.go | 4 +- .../sns_set_subscription_attributes_test.go | 161 +++++++++ 10 files changed, 620 insertions(+), 165 deletions(-) delete mode 100644 app/gosns/gosns_test.go create mode 100644 app/gosns/set_subscription_attributes.go create mode 100644 app/gosns/set_subscription_attributes_test.go create mode 100644 smoke_tests/sns_set_subscription_attributes_test.go diff --git a/app/gosns/get_subscription_attributes.go b/app/gosns/get_subscription_attributes.go index 2cd2e376..781375dc 100644 --- a/app/gosns/get_subscription_attributes.go +++ b/app/gosns/get_subscription_attributes.go @@ -23,47 +23,41 @@ func GetSubscriptionAttributesV1(req *http.Request) (int, interfaces.AbstractRes return utils.CreateErrorResponseV1("InvalidParameterValue", false) } - subscriptionArn := requestBody.SubscriptionArn - - for _, topic := range app.SyncTopics.Topics { - for _, sub := range topic.Subscriptions { - if sub.SubscriptionArn == subscriptionArn { - - entries := make([]models.SubscriptionAttributeEntry, 0, 0) - entry := models.SubscriptionAttributeEntry{Key: "Owner", Value: app.CurrentEnvironment.AccountID} - entries = append(entries, entry) - entry = models.SubscriptionAttributeEntry{Key: "RawMessageDelivery", Value: strconv.FormatBool(sub.Raw)} - entries = append(entries, entry) - entry = models.SubscriptionAttributeEntry{Key: "TopicArn", Value: sub.TopicArn} - entries = append(entries, entry) - entry = models.SubscriptionAttributeEntry{Key: "Endpoint", Value: sub.EndPoint} - entries = append(entries, entry) - entry = models.SubscriptionAttributeEntry{Key: "PendingConfirmation", Value: "false"} - entries = append(entries, entry) - entry = models.SubscriptionAttributeEntry{Key: "ConfirmationWasAuthenticated", Value: "true"} - entries = append(entries, entry) - entry = models.SubscriptionAttributeEntry{Key: "SubscriptionArn", Value: sub.SubscriptionArn} - entries = append(entries, entry) - entry = models.SubscriptionAttributeEntry{Key: "Protocol", Value: sub.Protocol} - entries = append(entries, entry) - - if sub.FilterPolicy != nil { - filterPolicyBytes, _ := json.Marshal(sub.FilterPolicy) - entry = models.SubscriptionAttributeEntry{Key: "FilterPolicy", Value: string(filterPolicyBytes)} - entries = append(entries, entry) - } + sub := getSubscription(requestBody.SubscriptionArn) + if sub == nil { + return utils.CreateErrorResponseV1("SubscriptionNotFound", false) + } - result := models.GetSubscriptionAttributesResult{Attributes: models.GetSubscriptionAttributes{Entries: entries}} - uuid := uuid.NewString() - respStruct := models.GetSubscriptionAttributesResponse{ - Xmlns: models.BASE_XMLNS, - Result: result, - Metadata: app.ResponseMetadata{RequestId: uuid}} + entries := make([]models.SubscriptionAttributeEntry, 0, 0) + entry := models.SubscriptionAttributeEntry{Key: "Owner", Value: app.CurrentEnvironment.AccountID} + entries = append(entries, entry) + entry = models.SubscriptionAttributeEntry{Key: "RawMessageDelivery", Value: strconv.FormatBool(sub.Raw)} + entries = append(entries, entry) + entry = models.SubscriptionAttributeEntry{Key: "TopicArn", Value: sub.TopicArn} + entries = append(entries, entry) + entry = models.SubscriptionAttributeEntry{Key: "Endpoint", Value: sub.EndPoint} + entries = append(entries, entry) + entry = models.SubscriptionAttributeEntry{Key: "PendingConfirmation", Value: "false"} + entries = append(entries, entry) + entry = models.SubscriptionAttributeEntry{Key: "ConfirmationWasAuthenticated", Value: "true"} + entries = append(entries, entry) + entry = models.SubscriptionAttributeEntry{Key: "SubscriptionArn", Value: sub.SubscriptionArn} + entries = append(entries, entry) + entry = models.SubscriptionAttributeEntry{Key: "Protocol", Value: sub.Protocol} + entries = append(entries, entry) + + if sub.FilterPolicy != nil { + filterPolicyBytes, _ := json.Marshal(sub.FilterPolicy) + entry = models.SubscriptionAttributeEntry{Key: "FilterPolicy", Value: string(filterPolicyBytes)} + entries = append(entries, entry) + } - return http.StatusOK, respStruct + result := models.GetSubscriptionAttributesResult{Attributes: models.GetSubscriptionAttributes{Entries: entries}} + uuid := uuid.NewString() + respStruct := models.GetSubscriptionAttributesResponse{ + Xmlns: models.BASE_XMLNS, + Result: result, + Metadata: app.ResponseMetadata{RequestId: uuid}} - } - } - } - return utils.CreateErrorResponseV1("SubscriptionNotFound", false) + return http.StatusOK, respStruct } diff --git a/app/gosns/gosns.go b/app/gosns/gosns.go index 93474392..e920248c 100644 --- a/app/gosns/gosns.go +++ b/app/gosns/gosns.go @@ -25,7 +25,6 @@ import ( "math/big" "github.com/Admiral-Piett/goaws/app" - "github.com/Admiral-Piett/goaws/app/common" log "github.com/sirupsen/logrus" ) @@ -144,55 +143,6 @@ func ConfirmSubscription(w http.ResponseWriter, req *http.Request) { } -func SetSubscriptionAttributes(w http.ResponseWriter, req *http.Request) { - content := req.FormValue("ContentType") - subsArn := req.FormValue("SubscriptionArn") - Attribute := req.FormValue("AttributeName") - Value := req.FormValue("AttributeValue") - - for _, topic := range app.SyncTopics.Topics { - for _, sub := range topic.Subscriptions { - if sub.SubscriptionArn == subsArn { - if Attribute == "RawMessageDelivery" { - app.SyncTopics.Lock() - if Value == "true" { - sub.Raw = true - } else { - sub.Raw = false - } - app.SyncTopics.Unlock() - //Good Response == return - uuid, _ := common.NewUUID() - respStruct := app.SetSubscriptionAttributesResponse{"http://queue.amazonaws.com/doc/2012-11-05/", app.ResponseMetadata{RequestId: uuid}} - SendResponseBack(w, req, respStruct, content) - return - } - - if Attribute == "FilterPolicy" { - filterPolicy := &app.FilterPolicy{} - err := json.Unmarshal([]byte(Value), filterPolicy) - if err != nil { - createErrorResponse(w, req, "ValidationError") - return - } - - app.SyncTopics.Lock() - sub.FilterPolicy = filterPolicy - app.SyncTopics.Unlock() - - //Good Response == return - uuid, _ := common.NewUUID() - respStruct := app.SetSubscriptionAttributesResponse{"http://queue.amazonaws.com/doc/2012-11-05/", app.ResponseMetadata{RequestId: uuid}} - SendResponseBack(w, req, respStruct, content) - return - } - - } - } - } - createErrorResponse(w, req, "SubscriptionNotFound") -} - // NOTE: The use case for this is to use GoAWS to call some external system with the message payload. Essentially // it is a localized subscription to some non-AWS endpoint. func callEndpoint(endpoint string, subArn string, msg app.SNSMessage, raw bool) error { @@ -277,6 +227,17 @@ func extractMessageFromJSON(msg string, protocol string) (string, error) { return defaultMsg, nil } +func getSubscription(subsArn string) *app.Subscription { + for _, topic := range app.SyncTopics.Topics { + for _, sub := range topic.Subscriptions { + if sub.SubscriptionArn == subsArn { + return sub + } + } + } + return nil +} + func createErrorResponse(w http.ResponseWriter, req *http.Request, err string) { er := models.SnsErrors[err] respStruct := models.ErrorResponse{ diff --git a/app/gosns/gosns_test.go b/app/gosns/gosns_test.go deleted file mode 100644 index 9032e4cd..00000000 --- a/app/gosns/gosns_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package gosns - -import ( - "net/http" - "net/http/httptest" - "net/url" - "strings" - "testing" - - "github.com/Admiral-Piett/goaws/app/test" - - "github.com/Admiral-Piett/goaws/app" - "github.com/Admiral-Piett/goaws/app/common" -) - -func TestSetSubscriptionAttributesHandler_FilterPolicy_POST_Success(t *testing.T) { - // Create a request to pass to our handler. We don't have any query parameters for now, so we'll - // pass 'nil' as the third parameter. - req, err := http.NewRequest("POST", "/", nil) - if err != nil { - t.Fatal(err) - } - - defer func() { - test.ResetApp() - }() - - topicName := "testing" - topicArn := "arn:aws:sns:" + app.CurrentEnvironment.Region + ":000000000000:" + topicName - subArn, _ := common.NewUUID() - subArn = topicArn + ":" + subArn - app.SyncTopics.Topics[topicName] = &app.Topic{Name: topicName, Arn: topicArn, Subscriptions: []*app.Subscription{ - { - SubscriptionArn: subArn, - }, - }} - - form := url.Values{} - form.Add("SubscriptionArn", subArn) - form.Add("AttributeName", "FilterPolicy") - form.Add("AttributeValue", "{\"foo\": [\"bar\"]}") - req.PostForm = form - - // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. - rr := httptest.NewRecorder() - handler := http.HandlerFunc(SetSubscriptionAttributes) - - // Our handlers satisfy http.Handler, so we can call their ServeHTTP method - // directly and pass in our Request and ResponseRecorder. - handler.ServeHTTP(rr, req) - - // Check the status code is what we expect. - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } - - // Check the response body is what we expect. - expected := "" - if !strings.Contains(rr.Body.String(), expected) { - t.Errorf("handler returned unexpected body: got %v want %v", - rr.Body.String(), expected) - } - - actualFilterPolicy := app.SyncTopics.Topics[topicName].Subscriptions[0].FilterPolicy - if (*actualFilterPolicy)["foo"][0] != "bar" { - t.Errorf("filter policy has not need applied") - } -} diff --git a/app/gosns/set_subscription_attributes.go b/app/gosns/set_subscription_attributes.go new file mode 100644 index 00000000..ef285e62 --- /dev/null +++ b/app/gosns/set_subscription_attributes.go @@ -0,0 +1,66 @@ +package gosns + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/Admiral-Piett/goaws/app" + "github.com/Admiral-Piett/goaws/app/interfaces" + "github.com/Admiral-Piett/goaws/app/models" + "github.com/Admiral-Piett/goaws/app/utils" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +func SetSubscriptionAttributesV1(req *http.Request) (int, interfaces.AbstractResponseBody) { + requestBody := models.NewSetSubscriptionAttributesRequest() + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) + if !ok { + log.Error("Invalid Request - SetSubscriptionAttributesV1") + return utils.CreateErrorResponseV1("InvalidParameterValue", false) + } + + subsArn := requestBody.SubscriptionArn + attrName := requestBody.AttributeName + attrValue := requestBody.AttributeValue + + sub := getSubscription(subsArn) + if sub == nil { + return utils.CreateErrorResponseV1("SubscriptionNotFound", false) + } + + switch attrName { + case "RawMessageDelivery": + app.SyncTopics.Lock() + if attrValue == "true" { + sub.Raw = true + } else { + sub.Raw = false + } + app.SyncTopics.Unlock() + + case "FilterPolicy": + filterPolicy := &app.FilterPolicy{} + err := json.Unmarshal([]byte(attrValue), filterPolicy) + if err != nil { + return utils.CreateErrorResponseV1("InvalidParameterValue", false) + } + app.SyncTopics.Lock() + sub.FilterPolicy = filterPolicy + app.SyncTopics.Unlock() + + case "DeliveryPolicy", "FilterPolicyScope", "RedrivePolicy", "SubscriptionRoleArn": + log.Info(fmt.Sprintf("AttributeName [%s] is valid on AWS but it is not implemented.", attrName)) + + default: + return utils.CreateErrorResponseV1("InvalidParameterValue", false) + } + + uuid := uuid.NewString() + respStruct := models.SetSubscriptionAttributesResponse{ + Xmlns: models.BASE_XMLNS, + Metadata: app.ResponseMetadata{RequestId: uuid}} + + return http.StatusOK, respStruct +} diff --git a/app/gosns/set_subscription_attributes_test.go b/app/gosns/set_subscription_attributes_test.go new file mode 100644 index 00000000..d634b6e8 --- /dev/null +++ b/app/gosns/set_subscription_attributes_test.go @@ -0,0 +1,317 @@ +package gosns + +import ( + "net/http" + "testing" + + "github.com/Admiral-Piett/goaws/app" + "github.com/Admiral-Piett/goaws/app/conf" + "github.com/Admiral-Piett/goaws/app/interfaces" + "github.com/Admiral-Piett/goaws/app/models" + "github.com/Admiral-Piett/goaws/app/test" + "github.com/Admiral-Piett/goaws/app/utils" + "github.com/stretchr/testify/assert" +) + +func TestSetSubscriptionAttributesV1_success_SetRawMessageDelivery_true(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + localTopic1 := app.SyncTopics.Topics["local-topic1"] + sub := localTopic1.Subscriptions[0] + assert.False(t, sub.Raw) + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: sub.SubscriptionArn, + AttributeName: "RawMessageDelivery", + AttributeValue: "true", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetSubscriptionAttributesV1(r) + + assert.Equal(t, http.StatusOK, code) + + // Assert SubscriptionAttribute has been updated + assert.True(t, sub.Raw) +} + +func TestSetSubscriptionAttributesV1_success_SetRawMessageDelivery_false(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + localTopic1 := app.SyncTopics.Topics["local-topic1"] + sub := localTopic1.Subscriptions[1] + assert.True(t, sub.Raw) + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: sub.SubscriptionArn, + AttributeName: "RawMessageDelivery", + AttributeValue: "false", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetSubscriptionAttributesV1(r) + + assert.Equal(t, http.StatusOK, code) + + // Assert SubscriptionAttribute has been updated + assert.False(t, sub.Raw) +} + +func TestSetSubscriptionAttributesV1_success_SetFilterPolicy(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + localTopic1 := app.SyncTopics.Topics["local-topic1"] + sub := localTopic1.Subscriptions[0] + assert.Empty(t, sub.FilterPolicy) + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: sub.SubscriptionArn, + AttributeName: "FilterPolicy", + AttributeValue: "{\"foo\":[\"bar\"]}", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetSubscriptionAttributesV1(r) + + assert.Equal(t, http.StatusOK, code) + + // Assert SubscriptionAttribute has been updated + expectedFilterPolicy := make(app.FilterPolicy) + expectedFilterPolicy["foo"] = []string{"bar"} + assert.Equal(t, &expectedFilterPolicy, sub.FilterPolicy) +} + +func TestSetSubscriptionAttributesV1_error_SetFilterPolicy_invalid(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + localTopic1 := app.SyncTopics.Topics["local-topic1"] + sub := localTopic1.Subscriptions[0] + assert.Empty(t, sub.FilterPolicy) + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: sub.SubscriptionArn, + AttributeName: "FilterPolicy", + AttributeValue: "Not a json string", // Invalid value + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetSubscriptionAttributesV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} + +func TestSetSubscriptionAttributesV1_success_SetDeliveryPolicy(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + localTopic1 := app.SyncTopics.Topics["local-topic1"] + sub := localTopic1.Subscriptions[0] + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: sub.SubscriptionArn, + AttributeName: "DeliveryPolicy", + AttributeValue: "foo", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetSubscriptionAttributesV1(r) + + assert.Equal(t, http.StatusOK, code) +} + +func TestSetSubscriptionAttributesV1_success_SetFilterPolicyScope(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + localTopic1 := app.SyncTopics.Topics["local-topic1"] + sub := localTopic1.Subscriptions[0] + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: sub.SubscriptionArn, + AttributeName: "FilterPolicyScope", + AttributeValue: "foo", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetSubscriptionAttributesV1(r) + + assert.Equal(t, http.StatusOK, code) +} + +func TestSetSubscriptionAttributesV1_success_SetRedrivePolicy(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + localTopic1 := app.SyncTopics.Topics["local-topic1"] + sub := localTopic1.Subscriptions[0] + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: sub.SubscriptionArn, + AttributeName: "SubscriptionRoleArn", + AttributeValue: "foo", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetSubscriptionAttributesV1(r) + + assert.Equal(t, http.StatusOK, code) +} + +func TestSetSubscriptionAttributesV1_success_SetSubscriptionRoleArn(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + localTopic1 := app.SyncTopics.Topics["local-topic1"] + sub := localTopic1.Subscriptions[0] + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: sub.SubscriptionArn, + AttributeName: "SubscriptionRoleArn", + AttributeValue: "foo", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetSubscriptionAttributesV1(r) + + assert.Equal(t, http.StatusOK, code) +} + +func TestSetSubscriptionAttributesV1_error_InvalidAttribute(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + localTopic1 := app.SyncTopics.Topics["local-topic1"] + subscriptions := localTopic1.Subscriptions + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: subscriptions[1].SubscriptionArn, + AttributeName: "InvalidAttribute", + AttributeValue: "foo", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, response := SetSubscriptionAttributesV1(r) + errorResult := response.GetResult().(models.ErrorResult) + + expected := models.ErrorResult{ + Type: "InvalidParameterValue", + Code: "AWS.SimpleNotificationService.InvalidParameterValue", + Message: "An invalid or out-of-range value was supplied for the input parameter.", + } + + assert.Equal(t, http.StatusBadRequest, code) + assert.Equal(t, expected, errorResult) +} + +func TestSetSubscriptionAttributesV1_error_NonExistentSubscription(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "NoQueuesOrTopics") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetSubscriptionAttributesRequest) + *v = models.SetSubscriptionAttributesRequest{ + SubscriptionArn: "foo", + AttributeName: "RawMessageDelivery", + AttributeValue: "true", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, response := SetSubscriptionAttributesV1(r) + errorResult := response.GetResult().(models.ErrorResult) + + expected := models.ErrorResult{ + Type: "Not Found", + Code: "AWS.SimpleNotificationService.NonExistentSubscription", + Message: "The specified subscription does not exist for this wsdl version.", + } + assert.Equal(t, http.StatusNotFound, code) + assert.Equal(t, expected, errorResult) +} + +func TestSetSubscriptionAttributesV1_error_invalid_request(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + return false + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetSubscriptionAttributesV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} diff --git a/app/models/responses.go b/app/models/responses.go index 6bac9ca9..68dc76d9 100644 --- a/app/models/responses.go +++ b/app/models/responses.go @@ -523,6 +523,20 @@ func (r GetSubscriptionAttributesResponse) GetRequestId() string { return r.Metadata.RequestId } +/*** Set Subscription Attributes ***/ +type SetSubscriptionAttributesResponse struct { + Xmlns string `xml:"xmlns,attr"` + Metadata app.ResponseMetadata `xml:"ResponseMetadata"` +} + +func (r SetSubscriptionAttributesResponse) GetResult() interface{} { + return nil +} + +func (r SetSubscriptionAttributesResponse) GetRequestId() string { + return r.Metadata.RequestId +} + /*** List Subscriptions By Topic Response */ type ListSubscriptionsByTopicResult struct { NextToken string `xml:"NextToken"` // not implemented diff --git a/app/models/sns.go b/app/models/sns.go index 021b0184..60f7f42d 100644 --- a/app/models/sns.go +++ b/app/models/sns.go @@ -272,6 +272,21 @@ type GetSubscriptionAttributesRequest struct { func (r *GetSubscriptionAttributesRequest) SetAttributesFromForm(values url.Values) {} +// SetSubscriptionAttributes + +func NewSetSubscriptionAttributesRequest() *SetSubscriptionAttributesRequest { + return &SetSubscriptionAttributesRequest{} +} + +// Ref: https://docs.aws.amazon.com/sns/latest/api/API_SetSubscriptionAttributes.html +type SetSubscriptionAttributesRequest struct { + SubscriptionArn string `json:"SubscriptionArn" schema:"SubscriptionArn"` + AttributeName string `json:"AttributeName" schema:"AttributeName"` + AttributeValue string `json:"AttributeValue" schema:"AttributeValue"` +} + +func (r *SetSubscriptionAttributesRequest) SetAttributesFromForm(values url.Values) {} + // List Subscriptions By Topic func NewListSubscriptionsByTopicRequest() *ListSubscriptionsByTopicRequest { diff --git a/app/router/router.go b/app/router/router.go index bc38b741..1d560e96 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -87,13 +87,11 @@ var routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractR "DeleteTopic": sns.DeleteTopicV1, "ListSubscriptions": sns.ListSubscriptionsV1, "GetSubscriptionAttributes": sns.GetSubscriptionAttributesV1, + "SetSubscriptionAttributes": sns.SetSubscriptionAttributesV1, "ListSubscriptionsByTopic": sns.ListSubscriptionsByTopicV1, } var routingTable = map[string]http.HandlerFunc{ - // SNS - "SetSubscriptionAttributes": sns.SetSubscriptionAttributes, - // SNS Internal "ConfirmSubscription": sns.ConfirmSubscription, } diff --git a/app/router/router_test.go b/app/router/router_test.go index 354cde52..c209ee27 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -279,13 +279,11 @@ func TestActionHandler_v0_xml(t *testing.T) { "DeleteTopic": sns.DeleteTopicV1, "ListSubscriptions": sns.ListSubscriptionsV1, "GetSubscriptionAttributes": sns.GetSubscriptionAttributesV1, + "SetSubscriptionAttributes": sns.SetSubscriptionAttributesV1, "ListSubscriptionsByTopic": sns.ListSubscriptionsByTopicV1, } routingTable = map[string]http.HandlerFunc{ - // SNS - "SetSubscriptionAttributes": sns.SetSubscriptionAttributes, - // SNS Internal "ConfirmSubscription": sns.ConfirmSubscription, } diff --git a/smoke_tests/sns_set_subscription_attributes_test.go b/smoke_tests/sns_set_subscription_attributes_test.go new file mode 100644 index 00000000..deec2ded --- /dev/null +++ b/smoke_tests/sns_set_subscription_attributes_test.go @@ -0,0 +1,161 @@ +package smoke_tests + +import ( + "context" + "fmt" + "net/http" + "testing" + + af "github.com/Admiral-Piett/goaws/app/fixtures" + "github.com/Admiral-Piett/goaws/app/test" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/sns" + "github.com/aws/aws-sdk-go-v2/service/sqs" + "github.com/gavv/httpexpect/v2" + "github.com/stretchr/testify/assert" +) + +func Test_SetSubscriptionAttributes_json_success(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + test.ResetResources() + }() + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + snsClient := sns.NewFromConfig(sdkConfig) + sqsClient := sqs.NewFromConfig(sdkConfig) + + // Create a subscription + queueName := "new-queue-1" + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &queueName, + }) + topicName := "new-topic-1" + snsClient.CreateTopic(context.TODO(), &sns.CreateTopicInput{ + Name: &topicName, + }) + subResp, _ := snsClient.Subscribe(context.TODO(), &sns.SubscribeInput{ + Protocol: aws.String("sqs"), + TopicArn: aws.String(fmt.Sprintf("%s:%s", af.BASE_SNS_ARN, topicName)), + Attributes: map[string]string{}, + Endpoint: aws.String(fmt.Sprintf("%s:%s", af.BASE_SQS_ARN, queueName)), + ReturnSubscriptionArn: true, + }) + + // Check initial attribute + getResp, err := snsClient.GetSubscriptionAttributes(context.TODO(), &sns.GetSubscriptionAttributesInput{ + SubscriptionArn: subResp.SubscriptionArn, + }) + assert.Equal(t, "false", getResp.Attributes["RawMessageDelivery"]) + assert.Nil(t, err) + + // Target test: Set attribute + attrName := "RawMessageDelivery" + attrValue := "true" + _, err = snsClient.SetSubscriptionAttributes(context.TODO(), &sns.SetSubscriptionAttributesInput{ + SubscriptionArn: subResp.SubscriptionArn, + AttributeName: &attrName, + AttributeValue: &attrValue, + }) + assert.Nil(t, err) + + // Assert the attribute has been updated + getResp, err = snsClient.GetSubscriptionAttributes(context.TODO(), &sns.GetSubscriptionAttributesInput{ + SubscriptionArn: subResp.SubscriptionArn, + }) + assert.Equal(t, "true", getResp.Attributes["RawMessageDelivery"]) + assert.Nil(t, err) +} + +func Test_SetSubscriptionAttributes_json_error_SubscriptionNotExistence(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + test.ResetResources() + }() + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + snsClient := sns.NewFromConfig(sdkConfig) + + // Target test: Set attribute + subscriptionArn := "not existence sub" + attrName := "RawMessageDelivery" + attrValue := "true" + response, err := snsClient.SetSubscriptionAttributes(context.TODO(), &sns.SetSubscriptionAttributesInput{ + SubscriptionArn: &subscriptionArn, + AttributeName: &attrName, + AttributeValue: &attrValue, + }) + assert.Contains(t, err.Error(), "404") + assert.Contains(t, err.Error(), "AWS.SimpleNotificationService.NonExistentSubscription") + assert.Nil(t, response) +} + +func Test_SetSubscriptionAttributes_xml_success(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + test.ResetResources() + }() + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + snsClient := sns.NewFromConfig(sdkConfig) + sqsClient := sqs.NewFromConfig(sdkConfig) + + // Create a subscription + queueName := "new-queue-1" + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &queueName, + }) + topicName := "new-topic-1" + snsClient.CreateTopic(context.TODO(), &sns.CreateTopicInput{ + Name: &topicName, + }) + subResp, _ := snsClient.Subscribe(context.TODO(), &sns.SubscribeInput{ + Protocol: aws.String("sqs"), + TopicArn: aws.String(fmt.Sprintf("%s:%s", af.BASE_SNS_ARN, topicName)), + Attributes: map[string]string{}, + Endpoint: aws.String(fmt.Sprintf("%s:%s", af.BASE_SQS_ARN, queueName)), + ReturnSubscriptionArn: true, + }) + + // Check initial attribute + getResp, err := snsClient.GetSubscriptionAttributes(context.TODO(), &sns.GetSubscriptionAttributesInput{ + SubscriptionArn: subResp.SubscriptionArn, + }) + assert.Equal(t, "false", getResp.Attributes["RawMessageDelivery"]) + assert.Nil(t, err) + + // Target test: Set attribute + setSubscriptionAttributesXML := struct { + Action string `xml:"Action"` + Version string `xml:"Version"` + SubscriptionArn string `xml:"SubscriptionArn"` + AttributeName string `xml:"AttributeName"` + AttributeValue string `xml:"AttributeValue"` + }{ + Action: "SetSubscriptionAttributes", + Version: "2012-11-05", + SubscriptionArn: *subResp.SubscriptionArn, + AttributeName: "RawMessageDelivery", + AttributeValue: "true", + } + e := httpexpect.Default(t, server.URL) + e.POST("/"). + WithForm(setSubscriptionAttributesXML). + Expect(). + Status(http.StatusOK). + Body().Raw() + + // Assert the attribute has been updated + getResp, err = snsClient.GetSubscriptionAttributes(context.TODO(), &sns.GetSubscriptionAttributesInput{ + SubscriptionArn: subResp.SubscriptionArn, + }) + assert.Equal(t, "true", getResp.Attributes["RawMessageDelivery"]) + assert.Nil(t, err) +}