diff --git a/app/gosns/gosns.go b/app/gosns/gosns.go index 16b0f038..d23f7759 100644 --- a/app/gosns/gosns.go +++ b/app/gosns/gosns.go @@ -85,24 +85,6 @@ func createPemFile() (privkey *rsa.PrivateKey, pemkey []byte, err error) { return } -func ListTopics(w http.ResponseWriter, req *http.Request) { - content := req.FormValue("ContentType") - - respStruct := app.ListTopicsResponse{} - respStruct.Xmlns = "http://queue.amazonaws.com/doc/2012-11-05/" - uuid, _ := common.NewUUID() - respStruct.Metadata = app.ResponseMetadata{RequestId: uuid} - - respStruct.Result.Topics.Member = make([]app.TopicArnResult, 0, 0) - log.Println("Listing Topics") - for _, topic := range app.SyncTopics.Topics { - ta := app.TopicArnResult{TopicArn: topic.Arn} - respStruct.Result.Topics.Member = append(respStruct.Result.Topics.Member, ta) - } - - SendResponseBack(w, req, respStruct, content) -} - func signMessage(privkey *rsa.PrivateKey, snsMsg *app.SNSMessage) (string, error) { fs, err := formatSignature(snsMsg) if err != nil { diff --git a/app/gosns/gosns_test.go b/app/gosns/gosns_test.go index a5ddce98..25775a7a 100644 --- a/app/gosns/gosns_test.go +++ b/app/gosns/gosns_test.go @@ -15,36 +15,6 @@ import ( "github.com/Admiral-Piett/goaws/app/common" ) -func TestListTopicshandler_POST_NoTopics(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) - } - - // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. - rr := httptest.NewRecorder() - handler := http.HandlerFunc(ListTopics) - - // 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) - } -} - // TODO - add a subscription and I think this should work func TestListSubscriptionByTopicResponse_No_Owner(t *testing.T) { conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "Local") @@ -229,6 +199,10 @@ func TestGetSubscriptionAttributesHandler_POST_Success(t *testing.T) { t.Fatal(err) } + defer func() { + test.ResetApp() + }() + topicName := "testing" topicArn := "arn:aws:sns:" + app.CurrentEnvironment.Region + ":000000000000:" + topicName subArn, _ := common.NewUUID() @@ -293,6 +267,10 @@ func TestSetSubscriptionAttributesHandler_FilterPolicy_POST_Success(t *testing.T t.Fatal(err) } + defer func() { + test.ResetApp() + }() + topicName := "testing" topicArn := "arn:aws:sns:" + app.CurrentEnvironment.Region + ":000000000000:" + topicName subArn, _ := common.NewUUID() diff --git a/app/gosns/list_topics.go b/app/gosns/list_topics.go new file mode 100644 index 00000000..c7a94385 --- /dev/null +++ b/app/gosns/list_topics.go @@ -0,0 +1,40 @@ +package gosns + +import ( + "net/http" + + "github.com/google/uuid" + + "github.com/Admiral-Piett/goaws/app" + "github.com/Admiral-Piett/goaws/app/models" + "github.com/Admiral-Piett/goaws/app/utils" + + "github.com/Admiral-Piett/goaws/app/interfaces" + log "github.com/sirupsen/logrus" +) + +func ListTopicsV1(req *http.Request) (int, interfaces.AbstractResponseBody) { + requestBody := models.NewListTopicsRequest() + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) + if !ok { + log.Error("Invalid Request - ListTopicsV1") + return utils.CreateErrorResponseV1("InvalidParameterValue", false) + } + + log.Debug("Listing Topics") + arnList := make([]models.TopicArnResult, 0) + + for _, topic := range app.SyncTopics.Topics { + ta := models.TopicArnResult{TopicArn: topic.Arn} + arnList = append(arnList, ta) + } + + requestId := uuid.NewString() + respStruct := models.ListTopicsResponse{ + Xmlns: models.BASE_XMLNS, + Result: models.ListTopicsResult{Topics: models.TopicNamestype{Member: arnList}}, + Metadata: app.ResponseMetadata{RequestId: requestId}, + } + + return http.StatusOK, respStruct +} diff --git a/app/gosns/list_topics_test.go b/app/gosns/list_topics_test.go new file mode 100644 index 00000000..f025f1e7 --- /dev/null +++ b/app/gosns/list_topics_test.go @@ -0,0 +1,93 @@ +package gosns + +import ( + "fmt" + "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 TestListTopicsV1_NoTopics(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.ListTopicsRequest) + *v = models.ListTopicsRequest{ + NextToken: "", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, res := ListTopicsV1(r) + + response, _ := res.(models.ListTopicsResponse) + + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, models.BASE_XMLNS, response.Xmlns) + assert.NotEqual(t, "", response.Metadata) + + assert.Len(t, response.Result.Topics.Member, 0) +} + +func TestListTopicsV1_BaseTopics(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) { + v := resultingStruct.(*models.ListTopicsRequest) + *v = models.ListTopicsRequest{ + NextToken: "", + } + return true + } + + _, r := test.GenerateRequestInfo("POST", "/", nil, true) + code, res := ListTopicsV1(r) + + response, _ := res.(models.ListTopicsResponse) + + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, models.BASE_XMLNS, response.Xmlns) + assert.NotEqual(t, "", response.Metadata) + + assert.Len(t, response.Result.Topics.Member, 4) + + topicArnVisited := map[string]bool{} + + for _, member := range response.Result.Topics.Member { + _, ok := topicArnVisited[member.TopicArn] + assert.False(t, ok, fmt.Sprintf("Found duplicated listed arn entry: %s", member.TopicArn)) + topicArnVisited[member.TopicArn] = true + } +} + +func TestListTopicsV1_request_transformer_error(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, _ := ListTopicsV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} diff --git a/app/models/responses.go b/app/models/responses.go index a82e4bb4..db7d6eed 100644 --- a/app/models/responses.go +++ b/app/models/responses.go @@ -420,3 +420,30 @@ func (r PublishResponse) GetResult() interface{} { func (r PublishResponse) GetRequestId() string { return r.Metadata.RequestId } + +/*** List Topics ***/ +type TopicArnResult struct { + TopicArn string `xml:"TopicArn"` + NextToken string `xml:"NextToken"` // not implemented +} +type TopicNamestype struct { + Member []TopicArnResult `xml:"member"` +} + +type ListTopicsResult struct { + Topics TopicNamestype `xml:"Topics"` +} + +type ListTopicsResponse struct { + Xmlns string `xml:"xmlns,attr"` + Result ListTopicsResult `xml:"ListTopicsResult"` + Metadata app.ResponseMetadata `xml:"ResponseMetadata"` +} + +func (r ListTopicsResponse) GetResult() interface{} { + return r.Result +} + +func (r ListTopicsResponse) GetRequestId() string { + return r.Metadata.RequestId +} diff --git a/app/models/sns.go b/app/models/sns.go index 422e8cd2..a4859d1a 100644 --- a/app/models/sns.go +++ b/app/models/sns.go @@ -224,3 +224,15 @@ func (r *PublishRequest) SetAttributesFromForm(values url.Values) { } } } + +// ListTopics + +func NewListTopicsRequest() *ListTopicsRequest { + return &ListTopicsRequest{} +} + +type ListTopicsRequest struct { + NextToken string `json:"NextToken" schema:"NextToken"` // not implemented +} + +func (r *ListTopicsRequest) SetAttributesFromForm(values url.Values) {} diff --git a/app/router/router.go b/app/router/router.go index 48fa8571..e05412f7 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -79,15 +79,15 @@ var routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractR "DeleteMessageBatch": sqs.DeleteMessageBatchV1, // SNS - "CreateTopic": sns.CreateTopicV1, "Subscribe": sns.SubscribeV1, "Unsubscribe": sns.UnsubscribeV1, "Publish": sns.PublishV1, + "ListTopics": sns.ListTopicsV1, + "CreateTopic": sns.CreateTopicV1, } var routingTable = map[string]http.HandlerFunc{ // SNS - "ListTopics": sns.ListTopics, "DeleteTopic": sns.DeleteTopic, "SetSubscriptionAttributes": sns.SetSubscriptionAttributes, "GetSubscriptionAttributes": sns.GetSubscriptionAttributes, diff --git a/app/router/router_test.go b/app/router/router_test.go index b2f918ae..c2b92438 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -271,15 +271,15 @@ func TestActionHandler_v0_xml(t *testing.T) { "DeleteMessageBatch": sqs.DeleteMessageBatchV1, // SNS - "CreateTopic": sns.CreateTopicV1, "Subscribe": sns.SubscribeV1, "Unsubscribe": sns.UnsubscribeV1, "Publish": sns.PublishV1, + "ListTopics": sns.ListTopicsV1, + "CreateTopic": sns.CreateTopicV1, } routingTable = map[string]http.HandlerFunc{ // SNS - "ListTopics": sns.ListTopics, "DeleteTopic": sns.DeleteTopic, "SetSubscriptionAttributes": sns.SetSubscriptionAttributes, "GetSubscriptionAttributes": sns.GetSubscriptionAttributes, diff --git a/app/sns_messages.go b/app/sns_messages.go index 5917e54c..d720f17b 100644 --- a/app/sns_messages.go +++ b/app/sns_messages.go @@ -1,24 +1,5 @@ package app -/*** List Topics Response */ -type TopicArnResult struct { - TopicArn string `xml:"TopicArn"` -} - -type TopicNamestype struct { - Member []TopicArnResult `xml:"member"` -} - -type ListTopicsResult struct { - Topics TopicNamestype `xml:"Topics"` -} - -type ListTopicsResponse struct { - Xmlns string `xml:"xmlns,attr"` - Result ListTopicsResult `xml:"ListTopicsResult"` - Metadata ResponseMetadata `xml:"ResponseMetadata"` -} - /*** Set Subscription Response ***/ type SetSubscriptionAttributesResponse struct { Xmlns string `xml:"xmlns,attr"` diff --git a/smoke_tests/fixtures/requests.go b/smoke_tests/fixtures/requests.go index 39cf15be..c8db931a 100644 --- a/smoke_tests/fixtures/requests.go +++ b/smoke_tests/fixtures/requests.go @@ -15,6 +15,14 @@ var ListQueuesRequestBodyXML = struct { Version: "2012-11-05", } +var ListTopicsRequestBodyXML = struct { + Action string `xml:"Action"` + Version string `xml:"Version"` +}{ + Action: "ListTopics", + Version: "2012-11-05", +} + var GetQueueAttributesRequestBodyXML = struct { Action string `xml:"Action"` Version string `xml:"Version"` diff --git a/smoke_tests/sns_create_topic_test.go b/smoke_tests/sns_create_topic_test.go index 2224e3c9..4dfe1679 100644 --- a/smoke_tests/sns_create_topic_test.go +++ b/smoke_tests/sns_create_topic_test.go @@ -6,7 +6,6 @@ import ( "net/http" "testing" - "github.com/Admiral-Piett/goaws/app" "github.com/Admiral-Piett/goaws/app/models" "github.com/Admiral-Piett/goaws/app/test" "github.com/aws/aws-sdk-go-v2/aws" @@ -50,7 +49,7 @@ func Test_CreateTopicV1_json_success(t *testing.T) { Expect(). Status(http.StatusOK). Body().Raw() - r2 := app.ListTopicsResponse{} + r2 := models.ListTopicsResponse{} xml.Unmarshal([]byte(r), &r2) assert.Equal(t, 1, len(r2.Result.Topics.Member)) assert.Contains(t, r2.Result.Topics.Member[0].TopicArn, topicName) @@ -97,7 +96,7 @@ func Test_CreateTopicV1_json_existant_topic(t *testing.T) { Expect(). Status(http.StatusOK). Body().Raw() - r2 := app.ListTopicsResponse{} + r2 := models.ListTopicsResponse{} xml.Unmarshal([]byte(r), &r2) assert.Equal(t, 1, len(r2.Result.Topics.Member)) assert.Contains(t, r2.Result.Topics.Member[0].TopicArn, topicName) @@ -145,7 +144,7 @@ func Test_CreateTopicV1_json_add_multiple_topics(t *testing.T) { Expect(). Status(http.StatusOK). Body().Raw() - r2 := app.ListTopicsResponse{} + r2 := models.ListTopicsResponse{} xml.Unmarshal([]byte(r), &r2) assert.Equal(t, 2, len(r2.Result.Topics.Member)) } @@ -191,7 +190,7 @@ func Test_CreateTopicV1_xml_success(t *testing.T) { Expect(). Status(http.StatusOK). Body().Raw() - r3 := app.ListTopicsResponse{} + r3 := models.ListTopicsResponse{} xml.Unmarshal([]byte(r), &r3) assert.Equal(t, 1, len(r3.Result.Topics.Member)) assert.Contains(t, r3.Result.Topics.Member[0].TopicArn, topicName) @@ -249,7 +248,7 @@ func Test_CreateTopicV1_xml_existant_topic(t *testing.T) { Expect(). Status(http.StatusOK). Body().Raw() - r3 := app.ListTopicsResponse{} + r3 := models.ListTopicsResponse{} xml.Unmarshal([]byte(r), &r3) assert.Equal(t, 1, len(r3.Result.Topics.Member)) assert.Contains(t, r3.Result.Topics.Member[0].TopicArn, topicName) diff --git a/smoke_tests/sns_list_topics_test.go b/smoke_tests/sns_list_topics_test.go new file mode 100644 index 00000000..41620ce0 --- /dev/null +++ b/smoke_tests/sns_list_topics_test.go @@ -0,0 +1,129 @@ +package smoke_tests + +import ( + "context" + "net/http" + "testing" + + "encoding/xml" + + "github.com/Admiral-Piett/goaws/app/models" + "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/stretchr/testify/assert" + + sf "github.com/Admiral-Piett/goaws/smoke_tests/fixtures" + + "github.com/gavv/httpexpect/v2" +) + +func Test_List_Topics_json_no_topics(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) + + sdkResponse, err := snsClient.ListTopics(context.TODO(), &sns.ListTopicsInput{}) + + assert.Nil(t, err) + assert.Len(t, sdkResponse.Topics, 0) +} + +func Test_List_Topics_json_multiple_topics(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) + + topicName1 := "topic-1" + snsClient.CreateTopic(context.TODO(), &sns.CreateTopicInput{ + Name: aws.String(topicName1), + }) + + topicName2 := "topic-2" + snsClient.CreateTopic(context.TODO(), &sns.CreateTopicInput{ + Name: aws.String(topicName2), + }) + + sdkResponse, err := snsClient.ListTopics(context.TODO(), &sns.ListTopicsInput{}) + + assert.Nil(t, err) + assert.Len(t, sdkResponse.Topics, 2) + assert.NotEqual(t, sdkResponse.Topics[0].TopicArn, sdkResponse.Topics[1].TopicArn) +} + +func Test_List_Topics_xml_no_topics(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + test.ResetResources() + }() + + e := httpexpect.Default(t, server.URL) + + r := e.POST("/"). + WithForm(sf.ListTopicsRequestBodyXML). + Expect(). + Status(http.StatusOK). + Body().Raw() + + listTopicsResponseObject := models.ListTopicsResponse{} + xml.Unmarshal([]byte(r), &listTopicsResponseObject) + + assert.Equal(t, "http://queue.amazonaws.com/doc/2012-11-05/", listTopicsResponseObject.Xmlns) + assert.Len(t, listTopicsResponseObject.Result.Topics.Member, 0) +} + +func Test_ListTopics_xml_multiple_topics(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) + + topicName1 := "topic-1" + snsClient.CreateTopic(context.TODO(), &sns.CreateTopicInput{ + Name: aws.String(topicName1), + }) + + topicName2 := "topic-2" + snsClient.CreateTopic(context.TODO(), &sns.CreateTopicInput{ + Name: aws.String(topicName2), + }) + + e := httpexpect.Default(t, server.URL) + + r := e.POST("/"). + WithForm(sf.ListTopicsRequestBodyXML). + Expect(). + Status(http.StatusOK). + Body().Raw() + + listTopicsResponseObject := models.ListTopicsResponse{} + xml.Unmarshal([]byte(r), &listTopicsResponseObject) + + assert.Equal(t, "http://queue.amazonaws.com/doc/2012-11-05/", listTopicsResponseObject.Xmlns) + assert.Len(t, listTopicsResponseObject.Result.Topics.Member, 2) + assert.NotEqual(t, listTopicsResponseObject.Result.Topics.Member[0].TopicArn, listTopicsResponseObject.Result.Topics.Member[1].TopicArn) +}