diff --git a/app/gosns/gosns.go b/app/gosns/gosns.go index 093228b8..8f3ef1b5 100644 --- a/app/gosns/gosns.go +++ b/app/gosns/gosns.go @@ -306,32 +306,6 @@ func GetSubscriptionAttributes(w http.ResponseWriter, req *http.Request) { createErrorResponse(w, req, "SubscriptionNotFound") } -func Unsubscribe(w http.ResponseWriter, req *http.Request) { - content := req.FormValue("ContentType") - subArn := req.FormValue("SubscriptionArn") - - log.Println("Unsubscribe:", subArn) - for _, topic := range app.SyncTopics.Topics { - for i, sub := range topic.Subscriptions { - if sub.SubscriptionArn == subArn { - app.SyncTopics.Lock() - - copy(topic.Subscriptions[i:], topic.Subscriptions[i+1:]) - topic.Subscriptions[len(topic.Subscriptions)-1] = nil - topic.Subscriptions = topic.Subscriptions[:len(topic.Subscriptions)-1] - - app.SyncTopics.Unlock() - - uuid, _ := common.NewUUID() - respStruct := app.UnsubscribeResponse{"http://queue.amazonaws.com/doc/2012-11-05/", app.ResponseMetadata{RequestId: uuid}} - SendResponseBack(w, req, respStruct, content) - return - } - } - } - createErrorResponse(w, req, "SubscriptionNotFound") -} - func DeleteTopic(w http.ResponseWriter, req *http.Request) { content := req.FormValue("ContentType") topicArn := req.FormValue("TopicArn") diff --git a/app/gosns/unsubscribe.go b/app/gosns/unsubscribe.go new file mode 100644 index 00000000..e0d21ef7 --- /dev/null +++ b/app/gosns/unsubscribe.go @@ -0,0 +1,45 @@ +package gosns + +import ( + "net/http" + + "github.com/google/uuid" + + "github.com/Admiral-Piett/goaws/app/models" + "github.com/Admiral-Piett/goaws/app/utils" + + "github.com/Admiral-Piett/goaws/app" + "github.com/Admiral-Piett/goaws/app/interfaces" + log "github.com/sirupsen/logrus" +) + +func UnsubscribeV1(req *http.Request) (int, interfaces.AbstractResponseBody) { + requestBody := models.NewUnsubscribeRequest() + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) + if !ok { + log.Error("Invalid Request - UnsubscribeV1") + return utils.CreateErrorResponseV1("InvalidParameterValue", false) + } + + log.Infof("Unsubscribe: %s", requestBody.SubscriptionArn) + for _, topic := range app.SyncTopics.Topics { + for i, sub := range topic.Subscriptions { + if sub.SubscriptionArn == requestBody.SubscriptionArn { + app.SyncTopics.Lock() + + copy(topic.Subscriptions[i:], topic.Subscriptions[i+1:]) + topic.Subscriptions[len(topic.Subscriptions)-1] = nil + topic.Subscriptions = topic.Subscriptions[:len(topic.Subscriptions)-1] + + app.SyncTopics.Unlock() + + respStruct := models.UnsubscribeResponse{ + Xmlns: models.BASE_XMLNS, + Metadata: app.ResponseMetadata{RequestId: uuid.NewString()}, + } + return http.StatusOK, respStruct + } + } + } + return utils.CreateErrorResponseV1("SubscriptionNotFound", false) +} diff --git a/app/gosns/unsubscribe_test.go b/app/gosns/unsubscribe_test.go new file mode 100644 index 00000000..49bf6cfd --- /dev/null +++ b/app/gosns/unsubscribe_test.go @@ -0,0 +1,83 @@ +package gosns + +import ( + "net/http" + "testing" + + "github.com/Admiral-Piett/goaws/app/fixtures" + + "github.com/Admiral-Piett/goaws/app/conf" + + "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/test" + "github.com/Admiral-Piett/goaws/app/utils" + "github.com/stretchr/testify/assert" +) + +func TestUnsubscribeV1_success(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + subArn := app.SyncTopics.Topics["unit-topic1"].Subscriptions[0].SubscriptionArn + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.UnsubscribeRequest) + *v = models.UnsubscribeRequest{ + SubscriptionArn: subArn, + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + status, response := UnsubscribeV1(r) + + assert.Equal(t, http.StatusOK, status) + _, ok := response.(models.UnsubscribeResponse) + + subs := app.SyncTopics.Topics["unit-topic1"].Subscriptions + assert.Len(t, subs, 0) + assert.True(t, ok) +} + +func TestUnsubscribeV1_invalid_request_body(t *testing.T) { + app.CurrentEnvironment = fixtures.LOCAL_ENVIRONMENT + 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) + status, _ := UnsubscribeV1(r) + + assert.Equal(t, http.StatusBadRequest, status) +} + +func TestUnsubscribeV1_invalid_subscription_arn(t *testing.T) { + app.CurrentEnvironment = fixtures.LOCAL_ENVIRONMENT + 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.UnsubscribeRequest) + *v = models.UnsubscribeRequest{ + SubscriptionArn: "garbage", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + status, _ := UnsubscribeV1(r) + + assert.Equal(t, http.StatusBadRequest, status) +} diff --git a/app/models/responses.go b/app/models/responses.go index b4ce42aa..07fa1e07 100644 --- a/app/models/responses.go +++ b/app/models/responses.go @@ -328,3 +328,17 @@ type ConfirmSubscriptionResponse struct { Result SubscribeResult `xml:"ConfirmSubscriptionResult"` Metadata app.ResponseMetadata `xml:"ResponseMetadata"` } + +/*** Delete Subscription ***/ +type UnsubscribeResponse struct { + Xmlns string `xml:"xmlns,attr"` + Metadata app.ResponseMetadata `xml:"ResponseMetadata"` +} + +func (r UnsubscribeResponse) GetResult() interface{} { + return nil +} + +func (r UnsubscribeResponse) GetRequestId() string { + return r.Metadata.RequestId +} diff --git a/app/models/sns.go b/app/models/sns.go index cbbb70b7..eab5352b 100644 --- a/app/models/sns.go +++ b/app/models/sns.go @@ -173,3 +173,13 @@ type SubscriptionAttributes struct { //ReplayPolicy string `json:"ReplayPolicy" schema:"ReplayPolicy"` //ReplayStatus string `json:"ReplayStatus" schema:"ReplayStatus"` } + +func NewUnsubscribeRequest() *UnsubscribeRequest { + return &UnsubscribeRequest{} +} + +type UnsubscribeRequest struct { + SubscriptionArn string `json:"SubscriptionArn" schema:"SubscriptionArn"` +} + +func (r *UnsubscribeRequest) SetAttributesFromForm(values url.Values) {} diff --git a/app/router/router.go b/app/router/router.go index 3ce2037f..6b7293fb 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -79,6 +79,7 @@ var routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractR // SNS "CreateTopic": sns.CreateTopicV1, "Subscribe": sns.SubscribeV1, + "Unsubscribe": sns.UnsubscribeV1, } var routingTable = map[string]http.HandlerFunc{ @@ -93,7 +94,6 @@ var routingTable = map[string]http.HandlerFunc{ "GetSubscriptionAttributes": sns.GetSubscriptionAttributes, "ListSubscriptionsByTopic": sns.ListSubscriptionsByTopic, "ListSubscriptions": sns.ListSubscriptions, - "Unsubscribe": sns.Unsubscribe, "Publish": sns.Publish, // SNS Internal diff --git a/app/router/router_test.go b/app/router/router_test.go index 9e8343b4..529ff9b5 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -271,6 +271,7 @@ func TestActionHandler_v0_xml(t *testing.T) { // SNS "CreateTopic": sns.CreateTopicV1, "Subscribe": sns.SubscribeV1, + "Unsubscribe": sns.UnsubscribeV1, } routingTable = map[string]http.HandlerFunc{ // SQS @@ -284,7 +285,6 @@ func TestActionHandler_v0_xml(t *testing.T) { "GetSubscriptionAttributes": sns.GetSubscriptionAttributes, "ListSubscriptionsByTopic": sns.ListSubscriptionsByTopic, "ListSubscriptions": sns.ListSubscriptions, - "Unsubscribe": sns.Unsubscribe, "Publish": sns.Publish, // SNS Internal diff --git a/app/sns_messages.go b/app/sns_messages.go index 10d373ae..3e4fff4a 100644 --- a/app/sns_messages.go +++ b/app/sns_messages.go @@ -93,12 +93,6 @@ type PublishResponse struct { Metadata ResponseMetadata `xml:"ResponseMetadata"` } -/*** Unsubscribe ***/ -type UnsubscribeResponse struct { - Xmlns string `xml:"xmlns,attr"` - Metadata ResponseMetadata `xml:"ResponseMetadata"` -} - /*** Delete Topic ***/ type DeleteTopicResponse struct { Xmlns string `xml:"xmlns,attr"` diff --git a/smoke_tests/sns_unsubscribe_test.go b/smoke_tests/sns_unsubscribe_test.go new file mode 100644 index 00000000..92c2bc7f --- /dev/null +++ b/smoke_tests/sns_unsubscribe_test.go @@ -0,0 +1,79 @@ +package smoke_tests + +import ( + "context" + "net/http" + "testing" + + "github.com/aws/aws-sdk-go-v2/config" + + "github.com/Admiral-Piett/goaws/app/conf" + "github.com/Admiral-Piett/goaws/app/test" + + "github.com/gavv/httpexpect/v2" + + "github.com/Admiral-Piett/goaws/app" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sns" + "github.com/stretchr/testify/assert" +) + +func Test_Unsubscribe_json(t *testing.T) { + server := generateServer() + defaultEnv := app.CurrentEnvironment + conf.LoadYamlConfig("../app/conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + server.Close() + test.ResetResources() + app.CurrentEnvironment = defaultEnv + }() + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + snsClient := sns.NewFromConfig(sdkConfig) + + subArn := app.SyncTopics.Topics["unit-topic1"].Subscriptions[0].SubscriptionArn + response, err := snsClient.Unsubscribe(context.TODO(), &sns.UnsubscribeInput{ + SubscriptionArn: &subArn, + }) + + assert.Nil(t, err) + assert.NotNil(t, response) + + app.SyncTopics.Lock() + defer app.SyncTopics.Unlock() + + subscriptions := app.SyncTopics.Topics["unit-topic1"].Subscriptions + assert.Len(t, subscriptions, 0) +} + +func Test_Unsubscribe_xml(t *testing.T) { + server := generateServer() + defaultEnv := app.CurrentEnvironment + conf.LoadYamlConfig("../app/conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + server.Close() + test.ResetResources() + app.CurrentEnvironment = defaultEnv + }() + + e := httpexpect.Default(t, server.URL) + + subArn := app.SyncTopics.Topics["unit-topic1"].Subscriptions[0].SubscriptionArn + requestBody := struct { + Action string `xml:"Action"` + SubscriptionArn string `schema:"SubscriptionArn"` + }{ + Action: "Unsubscribe", + SubscriptionArn: subArn, + } + + e.POST("/"). + WithForm(requestBody). + Expect(). + Status(http.StatusOK). + Body().Raw() + + subscriptions := app.SyncTopics.Topics["unit-topic1"].Subscriptions + assert.Len(t, subscriptions, 0) +}