From 1a058d0cdc55d2c56fbac86e62a6db723ae8da57 Mon Sep 17 00:00:00 2001 From: "Dai.Otsuka" Date: Wed, 4 Sep 2024 12:31:56 +0900 Subject: [PATCH] added confirm subscription update udpate fixed update --- app/gosns/confirm_subscription.go | 40 ++++++++ app/gosns/confirm_subscription_test.go | 125 +++++++++++++++++++++++++ app/gosns/gosns.go | 16 ---- app/models/responses.go | 18 +++- app/models/sns.go | 14 +++ app/router/router.go | 16 +--- app/router/router_test.go | 60 ------------ 7 files changed, 198 insertions(+), 91 deletions(-) create mode 100644 app/gosns/confirm_subscription.go create mode 100644 app/gosns/confirm_subscription_test.go diff --git a/app/gosns/confirm_subscription.go b/app/gosns/confirm_subscription.go new file mode 100644 index 00000000..3510d234 --- /dev/null +++ b/app/gosns/confirm_subscription.go @@ -0,0 +1,40 @@ +package gosns + +import ( + "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 ConfirmSubscriptionV1(req *http.Request) (int, interfaces.AbstractResponseBody) { + requestBody := models.NewConfirmSubscriptionRequest() + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) + if !ok { + log.Error("Invalid Request - ConfirmSubscriptionV1") + return utils.CreateErrorResponseV1("InvalidParameterValue", false) + } + topicArn := requestBody.TopicArn + confirmToken := requestBody.Token + var pendingConfirm pendingConfirm + + if pending, ok := TOPIC_DATA[topicArn]; !ok { + return utils.CreateErrorResponseV1("SubscriptionNotFound", false) + } else { + pendingConfirm = *pending + } + + if pendingConfirm.token != confirmToken { + return utils.CreateErrorResponseV1("SubscriptionNotFound", false) + } + respStruct := models.ConfirmSubscriptionResponse{ + Xmlns: models.BASE_XMLNS, + Result: models.ConfirmSubscriptionResult{SubscriptionArn: pendingConfirm.subArn}, + Metadata: app.ResponseMetadata{RequestId: uuid.NewString()}, + } + return http.StatusOK, respStruct +} diff --git a/app/gosns/confirm_subscription_test.go b/app/gosns/confirm_subscription_test.go new file mode 100644 index 00000000..f1b687ef --- /dev/null +++ b/app/gosns/confirm_subscription_test.go @@ -0,0 +1,125 @@ +package gosns + +import ( + "net/http" + "testing" + + "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 TestConfirmSubscriptionV1_Success(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + TOPIC_DATA = make(map[string]*pendingConfirm) + }() + + topicArn := "test-topic-arn" + confirmToken := "test-token" + subscriptionArn := "test-sub-arn" + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.ConfirmSubscriptionRequest) + *v = models.ConfirmSubscriptionRequest{ + TopicArn: topicArn, + Token: confirmToken, + } + return true + } + // set pending subscription + TOPIC_DATA[topicArn] = &pendingConfirm{ + subArn: subscriptionArn, + token: confirmToken, + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, response := ConfirmSubscriptionV1(r) + + result := response.GetResult().(models.ConfirmSubscriptionResult) + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, subscriptionArn, result.SubscriptionArn) +} + +func TestConfirmSubscriptionV1_NotFoundSubscription(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "NoQueuesOrTopics") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + TOPIC_DATA = make(map[string]*pendingConfirm) + }() + + topicArn := "test-topic-arn" + confirmToken := "test-token" + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.ConfirmSubscriptionRequest) + *v = models.ConfirmSubscriptionRequest{ + TopicArn: topicArn, + Token: confirmToken, + } + return true + } + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, response := ConfirmSubscriptionV1(r) + result := response.GetResult().(models.ErrorResult) + assert.Equal(t, http.StatusNotFound, code) + assert.Contains(t, result.Message, "The specified subscription does not exist for this wsdl version.") +} + +func TestConfirmSubscriptionV1_MismatchToken(t *testing.T) { + + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + TOPIC_DATA = make(map[string]*pendingConfirm) + }() + + topicArn := "test-topic-arn" + confirmToken := "test-token" + subscriptionArn := "test-sub-arn" + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.ConfirmSubscriptionRequest) + *v = models.ConfirmSubscriptionRequest{ + TopicArn: topicArn, + Token: "dummy", + } + return true + } + + // set dummy subscription + TOPIC_DATA[topicArn] = &pendingConfirm{ + subArn: subscriptionArn, + token: confirmToken, + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, response := ConfirmSubscriptionV1(r) + result := response.GetResult().(models.ErrorResult) + assert.Equal(t, http.StatusNotFound, code) + assert.Contains(t, result.Message, "The specified subscription does not exist for this wsdl version.") +} + +func TestConfirmSubscriptionV1_TransformerError(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + test.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + TOPIC_DATA = make(map[string]*pendingConfirm) + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { + return false + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, _ := ConfirmSubscriptionV1(r) + assert.Equal(t, http.StatusBadRequest, code) +} diff --git a/app/gosns/gosns.go b/app/gosns/gosns.go index e920248c..02976bd3 100644 --- a/app/gosns/gosns.go +++ b/app/gosns/gosns.go @@ -8,8 +8,6 @@ import ( "net/http" "time" - "github.com/google/uuid" - "github.com/Admiral-Piett/goaws/app/models" "bytes" @@ -129,20 +127,6 @@ func formatSignature(msg *app.SNSMessage) (formated string, err error) { return } -func ConfirmSubscription(w http.ResponseWriter, req *http.Request) { - topicArn := req.Form.Get("TopicArn") - confirmToken := req.Form.Get("Token") - pendingConfirm := TOPIC_DATA[topicArn] - if pendingConfirm.token == confirmToken { - respStruct := models.ConfirmSubscriptionResponse{"http://queue.amazonaws.com/doc/2012-11-05/", models.SubscribeResult{SubscriptionArn: pendingConfirm.subArn}, app.ResponseMetadata{RequestId: uuid.NewString()}} - - SendResponseBack(w, req, respStruct, "application/xml") - } else { - createErrorResponse(w, req, "SubArnNotFound") - } - -} - // 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 { diff --git a/app/models/responses.go b/app/models/responses.go index 68dc76d9..04d7b327 100644 --- a/app/models/responses.go +++ b/app/models/responses.go @@ -359,9 +359,21 @@ func (r SubscribeResponse) GetRequestId() string { /*** ConfirmSubscriptionResponse ***/ type ConfirmSubscriptionResponse struct { - Xmlns string `xml:"xmlns,attr"` - Result SubscribeResult `xml:"ConfirmSubscriptionResult"` - Metadata app.ResponseMetadata `xml:"ResponseMetadata"` + Xmlns string `xml:"xmlns,attr"` + Result ConfirmSubscriptionResult `xml:"ConfirmSubscriptionResult"` + Metadata app.ResponseMetadata `xml:"ResponseMetadata"` +} + +type ConfirmSubscriptionResult struct { + SubscriptionArn string `xml:"SubscriptionArn"` +} + +func (r ConfirmSubscriptionResponse) GetResult() interface{} { + return r.Result +} + +func (r ConfirmSubscriptionResponse) GetRequestId() string { + return r.Metadata.RequestId } /*** Delete Subscription ***/ diff --git a/app/models/sns.go b/app/models/sns.go index 60f7f42d..910192ba 100644 --- a/app/models/sns.go +++ b/app/models/sns.go @@ -299,3 +299,17 @@ type ListSubscriptionsByTopicRequest struct { } func (r *ListSubscriptionsByTopicRequest) SetAttributesFromForm(values url.Values) {} + +// Confirm Subscription V1 + +func NewConfirmSubscriptionRequest() *ConfirmSubscriptionRequest { + return &ConfirmSubscriptionRequest{} +} + +type ConfirmSubscriptionRequest struct { + AuthenticateOnUnsubscribe bool `json:"AuthenticateOnUnsubscribe" schema:"AuthenticateOnUnsubscribe"` // not implemented + TopicArn string `json:"TopicArn" schema:"TopicArn"` + Token string `json:"Token" schema:"Token"` +} + +func (r *ConfirmSubscriptionRequest) SetAttributesFromForm(values url.Values) {} diff --git a/app/router/router.go b/app/router/router.go index 1d560e96..66e184ef 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -89,11 +89,9 @@ var routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractR "GetSubscriptionAttributes": sns.GetSubscriptionAttributesV1, "SetSubscriptionAttributes": sns.SetSubscriptionAttributesV1, "ListSubscriptionsByTopic": sns.ListSubscriptionsByTopicV1, -} -var routingTable = map[string]http.HandlerFunc{ // SNS Internal - "ConfirmSubscription": sns.ConfirmSubscription, + "ConfirmSubscription": sns.ConfirmSubscriptionV1, } func health(w http.ResponseWriter, req *http.Request) { @@ -115,15 +113,9 @@ func actionHandler(w http.ResponseWriter, req *http.Request) { encodeResponse(w, req, statusCode, responseBody) return } - fn, ok := routingTable[action] - if !ok { - log.Println("Bad Request - Action:", action) - w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "Bad Request") - return - } - - http.HandlerFunc(fn).ServeHTTP(w, req) + log.Println("Bad Request - Action:", action) + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "Bad Request") } func pemHandler(w http.ResponseWriter, req *http.Request) { diff --git a/app/router/router_test.go b/app/router/router_test.go index c209ee27..0a2a3fb5 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -17,8 +17,6 @@ import ( "github.com/Admiral-Piett/goaws/app/interfaces" - sns "github.com/Admiral-Piett/goaws/app/gosns" - sqs "github.com/Admiral-Piett/goaws/app/gosqs" "github.com/stretchr/testify/assert" @@ -251,61 +249,3 @@ func TestActionHandler_v1_xml(t *testing.T) { xml.Unmarshal(w.Body.Bytes(), &tmp) assert.Equal(t, mocks.BaseResponse{Message: "response-body"}, tmp) } - -func TestActionHandler_v0_xml(t *testing.T) { - defer func() { - routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractResponseBody){ - // SQS - "CreateQueue": sqs.CreateQueueV1, - "ListQueues": sqs.ListQueuesV1, - "GetQueueAttributes": sqs.GetQueueAttributesV1, - "SetQueueAttributes": sqs.SetQueueAttributesV1, - "SendMessage": sqs.SendMessageV1, - "ReceiveMessage": sqs.ReceiveMessageV1, - "DeleteMessage": sqs.DeleteMessageV1, - "ChangeMessageVisibility": sqs.ChangeMessageVisibilityV1, - "GetQueueUrl": sqs.GetQueueUrlV1, - "PurgeQueue": sqs.PurgeQueueV1, - "DeleteQueue": sqs.DeleteQueueV1, - "SendMessageBatch": sqs.SendMessageBatchV1, - "DeleteMessageBatch": sqs.DeleteMessageBatchV1, - - // SNS - "Subscribe": sns.SubscribeV1, - "Unsubscribe": sns.UnsubscribeV1, - "Publish": sns.PublishV1, - "ListTopics": sns.ListTopicsV1, - "CreateTopic": sns.CreateTopicV1, - "DeleteTopic": sns.DeleteTopicV1, - "ListSubscriptions": sns.ListSubscriptionsV1, - "GetSubscriptionAttributes": sns.GetSubscriptionAttributesV1, - "SetSubscriptionAttributes": sns.SetSubscriptionAttributesV1, - "ListSubscriptionsByTopic": sns.ListSubscriptionsByTopicV1, - } - - routingTable = map[string]http.HandlerFunc{ - // SNS Internal - "ConfirmSubscription": sns.ConfirmSubscription, - } - }() - - mockCalled := false - mockFunction := func(w http.ResponseWriter, req *http.Request) { - mockCalled = true - w.WriteHeader(http.StatusOK) - } - routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractResponseBody){} - routingTable = map[string]http.HandlerFunc{ - "CreateQueue": mockFunction, - } - - w, r := test.GenerateRequestInfo("POST", "/url", nil, false) - form := url.Values{} - form.Add("Action", "CreateQueue") - r.PostForm = form - - actionHandler(w, r) - - assert.True(t, mockCalled) - assert.Equal(t, http.StatusOK, w.Code) -}