From 096af9a5cd1983626a5f34dd3ec02e09b2e5f11f Mon Sep 17 00:00:00 2001 From: Devin Humphreys Date: Wed, 1 May 2024 17:27:00 -0400 Subject: [PATCH] Add GetQueueAttributesV1 for JSON support --- app/conf/mock-data/mock-config.yaml | 1 + app/fixtures/sqs.go | 55 ++- app/gosqs/get_queue_attributes.go | 137 ++++++++ app/gosqs/get_queue_attributes_test.go | 178 ++++++++++ app/gosqs/gosqs.go | 100 +----- app/gosqs/gosqs_test.go | 139 -------- app/gosqs/queue_attributes_test.go | 4 +- app/models/models.go | 40 +++ app/models/models_test.go | 31 +- app/models/responses.go | 30 ++ app/models/responses_test.go | 28 ++ app/router/router.go | 6 +- app/router/router_test.go | 17 +- app/sqs_messages.go | 18 - smoke_tests/fixtures/responses.go | 22 +- smoke_tests/sqs_create_queue_test.go | 91 +++-- smoke_tests/sqs_get_queue_attributes_test.go | 335 +++++++++++++++++++ smoke_tests/utils.go | 2 +- 18 files changed, 911 insertions(+), 323 deletions(-) create mode 100644 app/gosqs/get_queue_attributes.go create mode 100644 app/gosqs/get_queue_attributes_test.go create mode 100644 app/models/responses_test.go create mode 100644 smoke_tests/sqs_get_queue_attributes_test.go diff --git a/app/conf/mock-data/mock-config.yaml b/app/conf/mock-data/mock-config.yaml index c042fff2..e5f1c31b 100644 --- a/app/conf/mock-data/mock-config.yaml +++ b/app/conf/mock-data/mock-config.yaml @@ -60,4 +60,5 @@ BaseUnitTests: Queues: # List of queues to create at startup - Name: unit-queue1 # Queue name - Name: unit-queue2 # Queue name + RedrivePolicy: '{"maxReceiveCount": 100, "deadLetterTargetArn":"arn:aws:sqs:us-east-1:100010001000:other-queue1"}' - Name: other-queue1 # Queue name diff --git a/app/fixtures/sqs.go b/app/fixtures/sqs.go index 96f538aa..6fbb9ec8 100644 --- a/app/fixtures/sqs.go +++ b/app/fixtures/sqs.go @@ -39,15 +39,54 @@ var CreateQueueResponse = models.CreateQueueResponse{ Metadata: models.BASE_RESPONSE_METADATA, } -var ListQueuesResult = models.ListQueuesResult{ - QueueUrls: []string{ - fmt.Sprintf("%s/%s", BASE_URL, "unit-queue1"), - fmt.Sprintf("%s/%s", BASE_URL, "unit-queue2"), - }, +var GetQueueAttributesRequest = models.GetQueueAttributesRequest{ + QueueUrl: fmt.Sprintf("%s/unit-queue1", BASE_URL), + AttributeNames: []string{"All"}, } -var ListQueuesResponse = models.ListQueuesResponse{ - Xmlns: models.BASE_XMLNS, - Result: ListQueuesResult, +var GetQueueAttributesResponse = models.GetQueueAttributesResponse{ + Xmlns: models.BASE_XMLNS, + Result: models.GetQueueAttributesResult{Attrs: []models.Attribute{ + models.Attribute{ + Name: "DelaySeconds", + Value: "0", + }, + models.Attribute{ + Name: "MaximumMessageSize", + Value: "262144", + }, + models.Attribute{ + Name: "MessageRetentionPeriod", + Value: "345600", + }, + models.Attribute{ + Name: "ReceiveMessageWaitTimeSeconds", + Value: "0", + }, + models.Attribute{ + Name: "VisibilityTimeout", + Value: "30", + }, + models.Attribute{ + Name: "ApproximateNumberOfMessages", + Value: "0", + }, + models.Attribute{ + Name: "ApproximateNumberOfMessagesNotVisible", + Value: "0", + }, + models.Attribute{ + Name: "CreatedTimestamp", + Value: "0000000000", + }, + models.Attribute{ + Name: "LastModifiedTimestamp", + Value: "0000000000", + }, + models.Attribute{ + Name: "QueueArn", + Value: "arn:aws:sqs:region:accountID:unit-queue1", + }, + }}, Metadata: models.BASE_RESPONSE_METADATA, } diff --git a/app/gosqs/get_queue_attributes.go b/app/gosqs/get_queue_attributes.go new file mode 100644 index 00000000..63b1d116 --- /dev/null +++ b/app/gosqs/get_queue_attributes.go @@ -0,0 +1,137 @@ +package gosqs + +import ( + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/Admiral-Piett/goaws/app" + "github.com/Admiral-Piett/goaws/app/models" + "github.com/Admiral-Piett/goaws/app/utils" + "github.com/mitchellh/copystructure" + + "github.com/Admiral-Piett/goaws/app/interfaces" + + log "github.com/sirupsen/logrus" +) + +func GetQueueAttributesV1(req *http.Request) (int, interfaces.AbstractResponseBody) { + requestBody := models.NewGetQueueAttributesRequest() + ok := utils.REQUEST_TRANSFORMER(requestBody, req) + if !ok { + log.Error("Invalid Request - GetQueueAttributesV1") + return createErrorResponseV1(ErrInvalidParameterValue.Type) + } + if requestBody.QueueUrl == "" { + log.Error("Missing QueueUrl - GetQueueAttributesV1") + return createErrorResponseV1(ErrInvalidParameterValue.Type) + } + + requestedAttributes := func() map[string]bool { + attrs := map[string]bool{} + if len(requestBody.AttributeNames) == 0 { + return map[string]bool{"All": true} + } + for _, attr := range requestBody.AttributeNames { + if "All" == attr { + return map[string]bool{"All": true} + } + attrs[attr] = true + } + return attrs + }() + + dupe, _ := copystructure.Copy(models.AVAILABLE_QUEUE_ATTRIBUTES) + includedAttributes, _ := dupe.(map[string]bool) + _, ok = requestedAttributes["All"] + if !ok { + for attr, _ := range includedAttributes { + _, ok := requestedAttributes[attr] + if !ok { + delete(includedAttributes, attr) + } + } + } + + uriSegments := strings.Split(requestBody.QueueUrl, "/") + queueName := uriSegments[len(uriSegments)-1] + + log.Infof("Get Queue Attributes: %s", queueName) + queueAttributes := make([]models.Attribute, 0, 0) + + app.SyncQueues.RLock() + queue, ok := app.SyncQueues.Queues[queueName] + if !ok { + log.Errorf("Get Queue URL: %s queue does not exist!!!", queueName) + app.SyncQueues.RUnlock() + return createErrorResponseV1(ErrInvalidParameterValue.Type) + } + + if _, ok := includedAttributes["DelaySeconds"]; ok { + attr := models.Attribute{Name: "DelaySeconds", Value: strconv.Itoa(queue.DelaySeconds)} + queueAttributes = append(queueAttributes, attr) + } + if _, ok := includedAttributes["MaximumMessageSize"]; ok { + attr := models.Attribute{Name: "MaximumMessageSize", Value: strconv.Itoa(queue.MaximumMessageSize)} + queueAttributes = append(queueAttributes, attr) + } + if _, ok := includedAttributes["MessageRetentionPeriod"]; ok { + attr := models.Attribute{Name: "MessageRetentionPeriod", Value: strconv.Itoa(queue.MessageRetentionPeriod)} + queueAttributes = append(queueAttributes, attr) + } + if _, ok := includedAttributes["ReceiveMessageWaitTimeSeconds"]; ok { + attr := models.Attribute{Name: "ReceiveMessageWaitTimeSeconds", Value: strconv.Itoa(queue.ReceiveMessageWaitTimeSeconds)} + queueAttributes = append(queueAttributes, attr) + } + if _, ok := includedAttributes["VisibilityTimeout"]; ok { + attr := models.Attribute{Name: "VisibilityTimeout", Value: strconv.Itoa(queue.VisibilityTimeout)} + queueAttributes = append(queueAttributes, attr) + } + if _, ok := includedAttributes["ApproximateNumberOfMessages"]; ok { + attr := models.Attribute{Name: "ApproximateNumberOfMessages", Value: strconv.Itoa(len(queue.Messages))} + queueAttributes = append(queueAttributes, attr) + } + // TODO - implement + //if _, ok := includedAttributes["ApproximateNumberOfMessagesDelayed"]; ok { + // attr := models.Attribute{Name: "ApproximateNumberOfMessagesDelayed", Value: strconv.Itoa(len(queue.Messages))} + // queueAttributes = append(queueAttributes, attr) + //} + if _, ok := includedAttributes["ApproximateNumberOfMessagesNotVisible"]; ok { + attr := models.Attribute{Name: "ApproximateNumberOfMessagesNotVisible", Value: strconv.Itoa(numberOfHiddenMessagesInQueue(*queue))} + queueAttributes = append(queueAttributes, attr) + } + if _, ok := includedAttributes["CreatedTimestamp"]; ok { + attr := models.Attribute{Name: "CreatedTimestamp", Value: "0000000000"} + queueAttributes = append(queueAttributes, attr) + } + if _, ok := includedAttributes["LastModifiedTimestamp"]; ok { + attr := models.Attribute{Name: "LastModifiedTimestamp", Value: "0000000000"} + queueAttributes = append(queueAttributes, attr) + } + if _, ok := includedAttributes["QueueArn"]; ok { + attr := models.Attribute{Name: "QueueArn", Value: queue.Arn} + queueAttributes = append(queueAttributes, attr) + } + // TODO - implement + //if _, ok := includedAttributes["Policy"]; ok { + // attr := models.Attribute{Name: "Policy", Value: ""} + // queueAttributes = append(queueAttributes, attr) + //} + //if _, ok := includedAttributes["RedriveAllowPolicy"]; ok { + // attr := models.Attribute{Name: "RedriveAllowPolicy", Value: ""} + // queueAttributes = append(queueAttributes, attr) + //} + if _, ok := includedAttributes["RedrivePolicy"]; ok && queue.DeadLetterQueue != nil { + attr := models.Attribute{Name: "RedrivePolicy", Value: fmt.Sprintf(`{"maxReceiveCount":"%d", "deadLetterTargetArn":"%s"}`, queue.MaxReceiveCount, queue.DeadLetterQueue.Arn)} + queueAttributes = append(queueAttributes, attr) + } + app.SyncQueues.RUnlock() + + respStruct := models.GetQueueAttributesResponse{ + models.BASE_XMLNS, + models.GetQueueAttributesResult{Attrs: queueAttributes}, + models.BASE_RESPONSE_METADATA, + } + return http.StatusOK, respStruct +} diff --git a/app/gosqs/get_queue_attributes_test.go b/app/gosqs/get_queue_attributes_test.go new file mode 100644 index 00000000..ec713551 --- /dev/null +++ b/app/gosqs/get_queue_attributes_test.go @@ -0,0 +1,178 @@ +package gosqs + +import ( + "fmt" + "net/http" + "testing" + + "github.com/mitchellh/copystructure" + + "github.com/Admiral-Piett/goaws/app/conf" + + "github.com/Admiral-Piett/goaws/app/fixtures" + "github.com/Admiral-Piett/goaws/app/interfaces" + "github.com/Admiral-Piett/goaws/app/models" + "github.com/Admiral-Piett/goaws/app/utils" + "github.com/stretchr/testify/assert" +) + +func TestGetQueueAttributesV1_success_all(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + utils.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + v := resultingStruct.(*models.GetQueueAttributesRequest) + *v = fixtures.GetQueueAttributesRequest + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, response := GetQueueAttributesV1(r) + + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, fixtures.GetQueueAttributesResponse, response) +} + +func TestGetQueueAttributesV1_success_no_request_attrs_returns_all(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + utils.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + v := resultingStruct.(*models.GetQueueAttributesRequest) + *v = models.GetQueueAttributesRequest{ + QueueUrl: "unit-queue1", + } + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, response := GetQueueAttributesV1(r) + + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, fixtures.GetQueueAttributesResponse, response) +} + +func TestGetQueueAttributesV1_success_all_with_redrive_queue(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + utils.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + v := resultingStruct.(*models.GetQueueAttributesRequest) + *v = models.GetQueueAttributesRequest{ + QueueUrl: "unit-queue2", + AttributeNames: []string{"All"}, + } + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, response := GetQueueAttributesV1(r) + + dupe, _ := copystructure.Copy(fixtures.GetQueueAttributesResponse) + expectedResponse, _ := dupe.(models.GetQueueAttributesResponse) + expectedResponse.Result.Attrs[9].Value = fmt.Sprintf("%s:%s", fixtures.BASE_ARN, "unit-queue2") + expectedResponse.Result.Attrs = append(expectedResponse.Result.Attrs, + models.Attribute{ + Name: "RedrivePolicy", + Value: fmt.Sprintf(`{"maxReceiveCount":"100", "deadLetterTargetArn":"%s:%s"}`, fixtures.BASE_ARN, "other-queue1"), + }, + ) + + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, expectedResponse, response) +} + +func TestGetQueueAttributesV1_success_specific_fields(t *testing.T) { + conf.LoadYamlConfig("../conf/mock-data/mock-config.yaml", "BaseUnitTests") + defer func() { + utils.ResetApp() + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + v := resultingStruct.(*models.GetQueueAttributesRequest) + *v = models.GetQueueAttributesRequest{ + QueueUrl: fmt.Sprintf("%s/unit-queue1", fixtures.BASE_URL), + AttributeNames: []string{"DelaySeconds"}, + } + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, response := GetQueueAttributesV1(r) + + expectedResponse := models.GetQueueAttributesResponse{ + Xmlns: models.BASE_XMLNS, + Result: models.GetQueueAttributesResult{Attrs: []models.Attribute{ + models.Attribute{ + Name: "DelaySeconds", + Value: "0", + }, + }}, + Metadata: models.BASE_RESPONSE_METADATA, + } + + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, expectedResponse, response) +} + +func TestGetQueueAttributesV1_request_transformer_error(t *testing.T) { + defer func() { + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + return false + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, _ := GetQueueAttributesV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} + +func TestGetQueueAttributesV1_missing_queue_url_in_request_returns_error(t *testing.T) { + defer func() { + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + v := resultingStruct.(*models.GetQueueAttributesRequest) + *v = models.GetQueueAttributesRequest{ + QueueUrl: "", + AttributeNames: []string{}, + } + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, _ := GetQueueAttributesV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} + +func TestGetQueueAttributesV1_missing_queue_returns_error(t *testing.T) { + defer func() { + utils.REQUEST_TRANSFORMER = utils.TransformRequest + }() + + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + v := resultingStruct.(*models.GetQueueAttributesRequest) + *v = fixtures.GetQueueAttributesRequest + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, _ := GetQueueAttributesV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} diff --git a/app/gosqs/gosqs.go b/app/gosqs/gosqs.go index 4dc0f9c6..afbfe4c8 100644 --- a/app/gosqs/gosqs.go +++ b/app/gosqs/gosqs.go @@ -383,7 +383,7 @@ func ReceiveMessage(w http.ResponseWriter, req *http.Request) { } } - log.Println("Getting Message from Queue:", queueName) + log.Debugf("Getting Message from Queue:%s", queueName) app.SyncQueues.Lock() // Lock the Queues if len(app.SyncQueues.Queues[queueName].Messages) > 0 { @@ -755,104 +755,6 @@ func GetQueueUrl(w http.ResponseWriter, req *http.Request) { } } -func GetQueueAttributes(w http.ResponseWriter, req *http.Request) { - // Sent response type - w.Header().Set("Content-Type", "application/xml") - // Retrieve FormValues required - queueUrl := getQueueFromPath(req.FormValue("QueueUrl"), req.URL.String()) - - attribute_names := map[string]bool{} - - for field, value := range req.Form { - if strings.HasPrefix(field, "AttributeName.") { - attribute_names[value[0]] = true - } - } - - include_attr := func(a string) bool { - if len(attribute_names) == 0 { - return true - } - if _, ok := attribute_names[a]; ok { - return true - } - if _, ok := attribute_names["All"]; ok { - return true - } - return false - } - - queueName := "" - if queueUrl == "" { - vars := mux.Vars(req) - queueName = vars["queueName"] - } else { - uriSegments := strings.Split(queueUrl, "/") - queueName = uriSegments[len(uriSegments)-1] - } - - log.Println("Get Queue Attributes:", queueName) - app.SyncQueues.RLock() - if queue, ok := app.SyncQueues.Queues[queueName]; ok { - // Create, encode/xml and send response - attribs := make([]app.Attribute, 0, 0) - if include_attr("VisibilityTimeout") { - attr := app.Attribute{Name: "VisibilityTimeout", Value: strconv.Itoa(queue.VisibilityTimeout)} - attribs = append(attribs, attr) - } - if include_attr("DelaySeconds") { - attr := app.Attribute{Name: "DelaySeconds", Value: strconv.Itoa(queue.DelaySeconds)} - attribs = append(attribs, attr) - } - if include_attr("ReceiveMessageWaitTimeSeconds") { - attr := app.Attribute{Name: "ReceiveMessageWaitTimeSeconds", Value: strconv.Itoa(queue.ReceiveMessageWaitTimeSeconds)} - attribs = append(attribs, attr) - } - if include_attr("ApproximateNumberOfMessages") { - attr := app.Attribute{Name: "ApproximateNumberOfMessages", Value: strconv.Itoa(len(queue.Messages))} - attribs = append(attribs, attr) - } - if include_attr("ApproximateNumberOfMessagesNotVisible") { - attr := app.Attribute{Name: "ApproximateNumberOfMessagesNotVisible", Value: strconv.Itoa(numberOfHiddenMessagesInQueue(*queue))} - attribs = append(attribs, attr) - } - if include_attr("CreatedTimestamp") { - attr := app.Attribute{Name: "CreatedTimestamp", Value: "0000000000"} - attribs = append(attribs, attr) - } - if include_attr("LastModifiedTimestamp") { - attr := app.Attribute{Name: "LastModifiedTimestamp", Value: "0000000000"} - attribs = append(attribs, attr) - } - if include_attr("QueueArn") { - attr := app.Attribute{Name: "QueueArn", Value: queue.Arn} - attribs = append(attribs, attr) - } - - // TODO - why do we just return the name and NOT the actual ARN here? - deadLetterTargetArn := "" - if queue.DeadLetterQueue != nil { - deadLetterTargetArn = queue.DeadLetterQueue.Name - } - if include_attr("RedrivePolicy") { - attr := app.Attribute{Name: "RedrivePolicy", Value: fmt.Sprintf(`{"maxReceiveCount": "%d", "deadLetterTargetArn":"%s"}`, queue.MaxReceiveCount, deadLetterTargetArn)} - attribs = append(attribs, attr) - } - - result := app.GetQueueAttributesResult{Attrs: attribs} - respStruct := app.GetQueueAttributesResponse{"http://queue.amazonaws.com/doc/2012-11-05/", result, app.ResponseMetadata{RequestId: "00000000-0000-0000-0000-000000000000"}} - enc := xml.NewEncoder(w) - enc.Indent(" ", " ") - if err := enc.Encode(respStruct); err != nil { - log.Printf("error: %v\n", err) - } - } else { - log.Println("Get Queue URL:", queueName, ", queue does not exist!!!") - createErrorResponse(w, req, "QueueNotFound") - } - app.SyncQueues.RUnlock() -} - func SetQueueAttributes(w http.ResponseWriter, req *http.Request) { // Sent response type w.Header().Set("Content-Type", "application/xml") diff --git a/app/gosqs/gosqs_test.go b/app/gosqs/gosqs_test.go index 3d373b00..84e0314c 100644 --- a/app/gosqs/gosqs_test.go +++ b/app/gosqs/gosqs_test.go @@ -1750,145 +1750,6 @@ func TestSendMessage_POST_DelaySeconds(t *testing.T) { } } -func TestGetQueueAttributes_GetAllAttributes(t *testing.T) { - done := make(chan struct{}, 0) - go PeriodicTasks(1*time.Second, done) - - // create a queue - req, err := http.NewRequest("POST", "/", nil) - if err != nil { - t.Fatal(err) - } - - form := url.Values{} - form.Add("Action", "CreateQueue") - form.Add("QueueName", "get-queue-attributes") - form.Add("Version", "2012-11-05") - req.PostForm = form - - rr := httptest.NewRecorder() - status, _ := CreateQueueV1(req) - - assert.Equal(t, status, http.StatusOK) - - // get queue attributes - req, err = http.NewRequest("GET", "/queue/get-queue-attributes?Action=GetQueueAttributes&AttributeName.1=All", nil) - if err != nil { - t.Fatal(err) - } - - rr = httptest.NewRecorder() - http.HandlerFunc(GetQueueAttributes).ServeHTTP(rr, req) - - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got \n%v want %v", - status, http.StatusOK) - } - - resp := app.GetQueueAttributesResponse{} - err = xml.Unmarshal(rr.Body.Bytes(), &resp) - if err != nil { - t.Fatalf("unexpected unmarshal error: %s", err) - } - - hasAttribute := func(attrs []app.Attribute, name string) bool { - for _, attr := range attrs { - if attr.Name == name { - return true - } - } - return false - } - - ok := hasAttribute(resp.Result.Attrs, "VisibilityTimeout") && - hasAttribute(resp.Result.Attrs, "DelaySeconds") && - hasAttribute(resp.Result.Attrs, "ReceiveMessageWaitTimeSeconds") && - hasAttribute(resp.Result.Attrs, "ApproximateNumberOfMessages") && - hasAttribute(resp.Result.Attrs, "ApproximateNumberOfMessagesNotVisible") && - hasAttribute(resp.Result.Attrs, "CreatedTimestamp") && - hasAttribute(resp.Result.Attrs, "LastModifiedTimestamp") && - hasAttribute(resp.Result.Attrs, "QueueArn") && - hasAttribute(resp.Result.Attrs, "RedrivePolicy") - - if !ok { - t.Fatal("handler should return all attributes") - } - - done <- struct{}{} -} - -func TestGetQueueAttributes_GetSelectedAttributes(t *testing.T) { - done := make(chan struct{}, 0) - go PeriodicTasks(1*time.Second, done) - - // create a queue - req, err := http.NewRequest("POST", "/", nil) - if err != nil { - t.Fatal(err) - } - - form := url.Values{} - form.Add("Action", "CreateQueue") - form.Add("QueueName", "get-queue-attributes") - form.Add("Version", "2012-11-05") - req.PostForm = form - - rr := httptest.NewRecorder() - status, _ := CreateQueueV1(req) - - assert.Equal(t, status, http.StatusOK) - - // get queue attributes - req, err = http.NewRequest("GET", "/queue/get-queue-attributes?Action=GetQueueAttributes&AttributeName.1=ApproximateNumberOfMessages&AttributeName.2=ApproximateNumberOfMessagesNotVisible&AttributeName.2=ApproximateNumberOfMessagesNotVisible", nil) - if err != nil { - t.Fatal(err) - } - - rr = httptest.NewRecorder() - http.HandlerFunc(GetQueueAttributes).ServeHTTP(rr, req) - - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got \n%v want %v", - status, http.StatusOK) - } - - resp := app.GetQueueAttributesResponse{} - err = xml.Unmarshal(rr.Body.Bytes(), &resp) - if err != nil { - t.Fatalf("unexpected unmarshal error: %s", err) - } - - hasAttribute := func(attrs []app.Attribute, name string) bool { - for _, attr := range attrs { - if attr.Name == name { - return true - } - } - return false - } - - ok := hasAttribute(resp.Result.Attrs, "ApproximateNumberOfMessages") && - hasAttribute(resp.Result.Attrs, "ApproximateNumberOfMessagesNotVisible") - - if !ok { - t.Fatal("handler should return requested attributes") - } - - ok = !(hasAttribute(resp.Result.Attrs, "VisibilityTimeout") || - hasAttribute(resp.Result.Attrs, "DelaySeconds") || - hasAttribute(resp.Result.Attrs, "ReceiveMessageWaitTimeSeconds") || - hasAttribute(resp.Result.Attrs, "CreatedTimestamp") || - hasAttribute(resp.Result.Attrs, "LastModifiedTimestamp") || - hasAttribute(resp.Result.Attrs, "QueueArn") || - hasAttribute(resp.Result.Attrs, "RedrivePolicy")) - - if !ok { - t.Fatal("handler should return only requested attributes") - } - - done <- struct{}{} -} - func TestCreateErrorResponseV1(t *testing.T) { expectedResponse := models.ErrorResponse{ Result: models.ErrorResult{ diff --git a/app/gosqs/queue_attributes_test.go b/app/gosqs/queue_attributes_test.go index 7401f6b0..da471c78 100644 --- a/app/gosqs/queue_attributes_test.go +++ b/app/gosqs/queue_attributes_test.go @@ -29,7 +29,7 @@ func TestApplyQueueAttributes(t *testing.T) { u.Add("Attribute.2.Value", "60") u.Add("Attribute.3.Name", "Policy") u.Add("Attribute.4.Name", "RedrivePolicy") - u.Add("Attribute.4.Value", `{"maxReceiveCount": "4", "deadLetterTargetArn":"arn:aws:sqs::000000000000:failed-messages"}`) + u.Add("Attribute.4.Value", `{"maxReceiveCount":"4", "deadLetterTargetArn":"arn:aws:sqs::000000000000:failed-messages"}`) u.Add("Attribute.5.Name", "ReceiveMessageWaitTimeSeconds") u.Add("Attribute.5.Value", "20") if err := validateAndSetQueueAttributesFromForm(q, u); err != nil { @@ -50,7 +50,7 @@ func TestApplyQueueAttributes(t *testing.T) { q := &app.Queue{VisibilityTimeout: 30} u := url.Values{} u.Add("Attribute.1.Name", "RedrivePolicy") - u.Add("Attribute.1.Value", `{"maxReceiveCount": "4"}`) + u.Add("Attribute.1.Value", `{"maxReceiveCount":"4"}`) err := validateAndSetQueueAttributesFromForm(q, u) if err != ErrInvalidParameterValue { t.Fatalf("expected %s, got %s", ErrInvalidParameterValue, err) diff --git a/app/models/models.go b/app/models/models.go index cd6dcd2d..6e74af48 100644 --- a/app/models/models.go +++ b/app/models/models.go @@ -13,6 +13,23 @@ import ( var BASE_XMLNS = "http://queue.amazonaws.com/doc/2012-11-05/" var BASE_RESPONSE_METADATA = app.ResponseMetadata{RequestId: "00000000-0000-0000-0000-000000000000"} +var AVAILABLE_QUEUE_ATTRIBUTES = map[string]bool{ + "DelaySeconds": true, + "MaximumMessageSize": true, + "MessageRetentionPeriod": true, + "Policy": true, + "ReceiveMessageWaitTimeSeconds": true, + "VisibilityTimeout": true, + "RedrivePolicy": true, + "RedriveAllowPolicy": true, + "ApproximateNumberOfMessages": true, + "ApproximateNumberOfMessagesDelayed": true, + "ApproximateNumberOfMessagesNotVisible": true, + "CreatedTimestamp": true, + "LastModifiedTimestamp": true, + "QueueArn": true, +} + func NewCreateQueueRequest() *CreateQueueRequest { return &CreateQueueRequest{ Attributes: Attributes{ @@ -153,6 +170,29 @@ func (r *ListQueueRequest) SetAttributesFromForm(values url.Values) { r.QueueNamePrefix = values.Get("QueueNamePrefix") } +// TODO - test models and responses +func NewGetQueueAttributesRequest() *GetQueueAttributesRequest { + return &GetQueueAttributesRequest{} +} + +type GetQueueAttributesRequest struct { + QueueUrl string `json:"QueueUrl"` + AttributeNames []string `json:"AttributeNames"` +} + +func (r *GetQueueAttributesRequest) SetAttributesFromForm(values url.Values) { + r.QueueUrl = values.Get("QueueUrl") + // TODO - test me + for i := 1; true; i++ { + attrKey := fmt.Sprintf("AttributeName.%d", i) + attrValue := values.Get(attrKey) + if attrValue == "" { + break + } + r.AttributeNames = append(r.AttributeNames, attrValue) + } +} + // TODO - copy Attributes for SNS // TODO - there are FIFO attributes and things too diff --git a/app/models/models_test.go b/app/models/models_test.go index 4808a20a..a6ac828c 100644 --- a/app/models/models_test.go +++ b/app/models/models_test.go @@ -241,7 +241,7 @@ func TestNewListQueuesRequest_SetAttributesFromForm(t *testing.T) { assert.Equal(t, "queue-name-prefix", lqr.QueueNamePrefix) } -func TestNewListQueuesRequest_SetAttributesFromForm_invalid_max_results(t *testing.T) { +func TestListQueuesRequest_SetAttributesFromForm_invalid_max_results(t *testing.T) { form := url.Values{} form.Add("MaxResults", "1.0") form.Add("NextToken", "next-token") @@ -254,3 +254,32 @@ func TestNewListQueuesRequest_SetAttributesFromForm_invalid_max_results(t *testi assert.Equal(t, "next-token", lqr.NextToken) assert.Equal(t, "queue-name-prefix", lqr.QueueNamePrefix) } + +func TestGetQueueAttributesRequest_SetAttributesFromForm(t *testing.T) { + form := url.Values{} + form.Add("QueueUrl", "queue-url") + form.Add("AttributeName.1", "attribute-1") + form.Add("AttributeName.2", "attribute-2") + + lqr := &GetQueueAttributesRequest{} + lqr.SetAttributesFromForm(form) + + assert.Equal(t, "queue-url", lqr.QueueUrl) + assert.Equal(t, 2, len(lqr.AttributeNames)) + assert.Contains(t, lqr.AttributeNames, "attribute-1") + assert.Contains(t, lqr.AttributeNames, "attribute-2") +} + +func TestGetQueueAttributesRequest_SetAttributesFromForm_skips_invalid_key_sequence(t *testing.T) { + form := url.Values{} + form.Add("QueueUrl", "queue-url") + form.Add("AttributeName.1", "attribute-1") + form.Add("AttributeName.3", "attribute-3") + + lqr := &GetQueueAttributesRequest{} + lqr.SetAttributesFromForm(form) + + assert.Equal(t, "queue-url", lqr.QueueUrl) + assert.Equal(t, 1, len(lqr.AttributeNames)) + assert.Contains(t, lqr.AttributeNames, "attribute-1") +} diff --git a/app/models/responses.go b/app/models/responses.go index e977da4f..836f1114 100644 --- a/app/models/responses.go +++ b/app/models/responses.go @@ -63,3 +63,33 @@ func (r ListQueuesResponse) GetResult() interface{} { func (r ListQueuesResponse) GetRequestId() string { return r.Metadata.RequestId } + +/*** Get Queue Attributes ***/ +type Attribute struct { + Name string `xml:"Name,omitempty"` + Value string `xml:"Value,omitempty"` +} + +type GetQueueAttributesResult struct { + /* VisibilityTimeout, DelaySeconds, ReceiveMessageWaitTimeSeconds, ApproximateNumberOfMessages + ApproximateNumberOfMessagesNotVisible, CreatedTimestamp, LastModifiedTimestamp, QueueArn */ + Attrs []Attribute `xml:"Attribute,omitempty"` +} + +type GetQueueAttributesResponse struct { + Xmlns string `xml:"xmlns,attr,omitempty"` + Result GetQueueAttributesResult `xml:"GetQueueAttributesResult"` + Metadata app.ResponseMetadata `xml:"ResponseMetadata,omitempty"` +} + +func (r GetQueueAttributesResponse) GetResult() interface{} { + result := map[string]string{} + for _, attr := range r.Result.Attrs { + result[attr.Name] = attr.Value + } + return map[string]map[string]string{"Attributes": result} +} + +func (r GetQueueAttributesResponse) GetRequestId() string { + return r.Metadata.RequestId +} diff --git a/app/models/responses_test.go b/app/models/responses_test.go new file mode 100644 index 00000000..d9da276f --- /dev/null +++ b/app/models/responses_test.go @@ -0,0 +1,28 @@ +package models + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// NOTE: For now, we're only going to test those methods that do something other than just return a field + +func TestGetQueueAttributesResponse_GetResult(t *testing.T) { + gqa := GetQueueAttributesResponse{ + Result: GetQueueAttributesResult{Attrs: []Attribute{ + {Name: "attribute-name1", Value: "attribute-value1"}, + {Name: "attribute-name2", Value: "attribute-value2"}, + }}, + } + + expectedAttributes := map[string]map[string]string{ + "Attributes": { + "attribute-name1": "attribute-value1", + "attribute-name2": "attribute-value2", + }, + } + result := gqa.GetResult() + + assert.Equal(t, expectedAttributes, result) +} diff --git a/app/router/router.go b/app/router/router.go index f3ef1fb3..a92c46b1 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -60,13 +60,13 @@ func encodeResponse(w http.ResponseWriter, req *http.Request, statusCode int, bo // V1 - includes JSON Support (and of course the old XML). var routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractResponseBody){ - "CreateQueue": sqs.CreateQueueV1, - "ListQueues": sqs.ListQueuesV1, + "CreateQueue": sqs.CreateQueueV1, + "ListQueues": sqs.ListQueuesV1, + "GetQueueAttributes": sqs.GetQueueAttributesV1, } var routingTable = map[string]http.HandlerFunc{ // SQS - "GetQueueAttributes": sqs.GetQueueAttributes, "SetQueueAttributes": sqs.SetQueueAttributes, "SendMessage": sqs.SendMessage, "SendMessageBatch": sqs.SendMessageBatch, diff --git a/app/router/router_test.go b/app/router/router_test.go index 8f3d975b..cb341752 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -4,12 +4,15 @@ import ( "bytes" "encoding/json" "encoding/xml" + "fmt" "net/http" "net/http/httptest" "net/url" "strings" "testing" + af "github.com/Admiral-Piett/goaws/app/fixtures" + "github.com/Admiral-Piett/goaws/app/mocks" "github.com/Admiral-Piett/goaws/app/interfaces" @@ -81,7 +84,6 @@ func TestIndexServerhandler_POST_GoodRequest(t *testing.T) { } func TestIndexServerhandler_POST_GoodRequest_With_URL(t *testing.T) { - req, err := http.NewRequest("POST", "/100010001000/local-queue1", nil) if err != nil { t.Fatal(err) @@ -96,6 +98,7 @@ func TestIndexServerhandler_POST_GoodRequest_With_URL(t *testing.T) { form = url.Values{} form.Add("Action", "GetQueueAttributes") + form.Add("QueueUrl", fmt.Sprintf("%s/local-queue1", af.BASE_URL)) req.PostForm = form // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. @@ -105,11 +108,7 @@ func TestIndexServerhandler_POST_GoodRequest_With_URL(t *testing.T) { // directly and pass in our Request and ResponseRecorder. New().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) - } + assert.Equal(t, http.StatusOK, rr.Code) } func TestIndexServerhandler_POST_GoodRequest_With_URL_And_Aws_Json_Protocol(t *testing.T) { @@ -261,12 +260,12 @@ func TestActionHandler_v1_xml(t *testing.T) { func TestActionHandler_v0_xml(t *testing.T) { defer func() { routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractResponseBody){ - "CreateQueue": sqs.CreateQueueV1, - "ListQueues": sqs.ListQueuesV1, + "CreateQueue": sqs.CreateQueueV1, + "ListQueues": sqs.ListQueuesV1, + "GetQueueAttributes": sqs.GetQueueAttributesV1, } routingTable = map[string]http.HandlerFunc{ // SQS - "GetQueueAttributes": sqs.GetQueueAttributes, "SetQueueAttributes": sqs.SetQueueAttributes, "SendMessage": sqs.SendMessage, "SendMessageBatch": sqs.SendMessageBatch, diff --git a/app/sqs_messages.go b/app/sqs_messages.go index f8693277..314c89fa 100644 --- a/app/sqs_messages.go +++ b/app/sqs_messages.go @@ -129,24 +129,6 @@ type GetQueueUrlResponse struct { Metadata ResponseMetadata `xml:"ResponseMetadata,omitempty"` } -/*** Get Queue Attributes ***/ -type Attribute struct { - Name string `xml:"Name,omitempty"` - Value string `xml:"Value,omitempty"` -} - -type GetQueueAttributesResult struct { - /* VisibilityTimeout, DelaySeconds, ReceiveMessageWaitTimeSeconds, ApproximateNumberOfMessages - ApproximateNumberOfMessagesNotVisible, CreatedTimestamp, LastModifiedTimestamp, QueueArn */ - Attrs []Attribute `xml:"Attribute,omitempty"` -} - -type GetQueueAttributesResponse struct { - Xmlns string `xml:"xmlns,attr,omitempty"` - Result GetQueueAttributesResult `xml:"GetQueueAttributesResult"` - Metadata ResponseMetadata `xml:"ResponseMetadata,omitempty"` -} - type SetQueueAttributesResponse struct { Xmlns string `xml:"xmlns,attr,omitempty"` Metadata ResponseMetadata `xml:"ResponseMetadata,omitempty"` diff --git a/smoke_tests/fixtures/responses.go b/smoke_tests/fixtures/responses.go index 62ed5a66..0b4fbb5a 100644 --- a/smoke_tests/fixtures/responses.go +++ b/smoke_tests/fixtures/responses.go @@ -3,26 +3,36 @@ package fixtures import ( "fmt" + "github.com/Admiral-Piett/goaws/app/models" + af "github.com/Admiral-Piett/goaws/app/fixtures" "github.com/Admiral-Piett/goaws/app" ) -var BASE_GET_QUEUE_ATTRIBUTES_RESPONSE = app.GetQueueAttributesResponse{ +var BASE_GET_QUEUE_ATTRIBUTES_RESPONSE = models.GetQueueAttributesResponse{ Xmlns: "http://queue.amazonaws.com/doc/2012-11-05/", - Result: app.GetQueueAttributesResult{Attrs: []app.Attribute{ + Result: models.GetQueueAttributesResult{Attrs: []models.Attribute{ { - Name: "VisibilityTimeout", + Name: "DelaySeconds", Value: "0", }, { - Name: "DelaySeconds", + Name: "MaximumMessageSize", + Value: "0", + }, + { + Name: "MessageRetentionPeriod", Value: "0", }, { Name: "ReceiveMessageWaitTimeSeconds", Value: "0", }, + { + Name: "VisibilityTimeout", + Value: "0", + }, { Name: "ApproximateNumberOfMessages", Value: "0", @@ -43,10 +53,6 @@ var BASE_GET_QUEUE_ATTRIBUTES_RESPONSE = app.GetQueueAttributesResponse{ Name: "QueueArn", Value: fmt.Sprintf("%s:new-queue-1", af.BASE_ARN), }, - { - Name: "RedrivePolicy", - Value: "{\"maxReceiveCount\": \"0\", \"deadLetterTargetArn\":\"\"}", - }, }}, Metadata: app.ResponseMetadata{RequestId: REQUEST_ID}, } diff --git a/smoke_tests/sqs_create_queue_test.go b/smoke_tests/sqs_create_queue_test.go index 6641f081..1789badc 100644 --- a/smoke_tests/sqs_create_queue_test.go +++ b/smoke_tests/sqs_create_queue_test.go @@ -69,7 +69,7 @@ func Test_CreateQueueV1_json_no_attributes(t *testing.T) { Status(http.StatusOK). Body().Raw() - r3 := app.GetQueueAttributesResponse{} + r3 := models.GetQueueAttributesResponse{} xml.Unmarshal([]byte(r), &r3) assert.Equal(t, sf.BASE_GET_QUEUE_ATTRIBUTES_RESPONSE, r3) } @@ -116,14 +116,14 @@ func Test_CreateQueueV1_json_with_attributes(t *testing.T) { Status(http.StatusOK). Body().Raw() - exp2 := models.ListQueuesResponse{ - Xmlns: "http://queue.amazonaws.com/doc/2012-11-05/", - Result: models.ListQueuesResult{QueueUrls: []string{fmt.Sprintf("%s/%s", af.BASE_URL, redriveQueue), fmt.Sprintf("%s/new-queue-1", af.BASE_URL)}}, - Metadata: app.ResponseMetadata{RequestId: sf.REQUEST_ID}, - } r2 := models.ListQueuesResponse{} xml.Unmarshal([]byte(r), &r2) - assert.Equal(t, exp2, r2) + + assert.Equal(t, models.BASE_XMLNS, r2.Xmlns) + assert.Equal(t, models.BASE_RESPONSE_METADATA, r2.Metadata) + assert.Equal(t, 2, len(r2.Result.QueueUrls)) + assert.Contains(t, r2.Result.QueueUrls, fmt.Sprintf("%s/%s", af.BASE_URL, redriveQueue)) + assert.Contains(t, r2.Result.QueueUrls, fmt.Sprintf("%s/new-queue-1", af.BASE_URL)) r = e.POST("/"). WithForm(sf.GetQueueAttributesRequestBodyXML). @@ -132,12 +132,18 @@ func Test_CreateQueueV1_json_with_attributes(t *testing.T) { Body().Raw() dupe, _ := copystructure.Copy(sf.BASE_GET_QUEUE_ATTRIBUTES_RESPONSE) - exp3, _ := dupe.(app.GetQueueAttributesResponse) - exp3.Result.Attrs[0].Value = "5" - exp3.Result.Attrs[1].Value = "1" - exp3.Result.Attrs[2].Value = "4" - exp3.Result.Attrs[8].Value = fmt.Sprintf(`{"maxReceiveCount": "100", "deadLetterTargetArn":"%s"}`, redriveQueue) - r3 := app.GetQueueAttributesResponse{} + exp3, _ := dupe.(models.GetQueueAttributesResponse) + exp3.Result.Attrs[0].Value = "1" + exp3.Result.Attrs[1].Value = "2" + exp3.Result.Attrs[2].Value = "3" + exp3.Result.Attrs[3].Value = "4" + exp3.Result.Attrs[4].Value = "5" + exp3.Result.Attrs[9].Value = fmt.Sprintf("%s:%s", af.BASE_ARN, af.QueueName) + exp3.Result.Attrs = append(exp3.Result.Attrs, models.Attribute{ + Name: "RedrivePolicy", + Value: fmt.Sprintf(`{"maxReceiveCount":"100", "deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, redriveQueue), + }) + r3 := models.GetQueueAttributesResponse{} xml.Unmarshal([]byte(r), &r3) assert.Equal(t, exp3, r3) } @@ -189,11 +195,15 @@ func Test_CreateQueueV1_json_with_attributes_as_ints(t *testing.T) { Body().Raw() dupe, _ := copystructure.Copy(sf.BASE_GET_QUEUE_ATTRIBUTES_RESPONSE) - exp3, _ := dupe.(app.GetQueueAttributesResponse) - exp3.Result.Attrs[0].Value = "5" - exp3.Result.Attrs[1].Value = "1" - exp3.Result.Attrs[2].Value = "4" - r3 := app.GetQueueAttributesResponse{} + exp3, _ := dupe.(models.GetQueueAttributesResponse) + exp3.Result.Attrs[0].Value = "1" + exp3.Result.Attrs[1].Value = "2" + exp3.Result.Attrs[2].Value = "3" + exp3.Result.Attrs[3].Value = "4" + exp3.Result.Attrs[4].Value = "5" + exp3.Result.Attrs[9].Value = fmt.Sprintf("%s:%s", af.BASE_ARN, af.QueueName) + + r3 := models.GetQueueAttributesResponse{} xml.Unmarshal([]byte(r), &r3) assert.Equal(t, exp3, r3) } @@ -292,12 +302,18 @@ func Test_CreateQueueV1_json_with_attributes_ints_as_strings(t *testing.T) { Body().Raw() dupe, _ := copystructure.Copy(sf.BASE_GET_QUEUE_ATTRIBUTES_RESPONSE) - exp3, _ := dupe.(app.GetQueueAttributesResponse) - exp3.Result.Attrs[0].Value = "30" - exp3.Result.Attrs[1].Value = "1" - exp3.Result.Attrs[7].Value = fmt.Sprintf("%s:new-string-queue", af.BASE_ARN) - exp3.Result.Attrs[8].Value = "{\"maxReceiveCount\": \"100\", \"deadLetterTargetArn\":\"new-queue-1\"}" - r3 := app.GetQueueAttributesResponse{} + exp3, _ := dupe.(models.GetQueueAttributesResponse) + exp3.Result.Attrs[0].Value = "1" + exp3.Result.Attrs[1].Value = "2" + exp3.Result.Attrs[2].Value = "3" + exp3.Result.Attrs[3].Value = "0" + exp3.Result.Attrs[4].Value = "30" + exp3.Result.Attrs[9].Value = fmt.Sprintf("%s:new-string-queue", af.BASE_ARN) + exp3.Result.Attrs = append(exp3.Result.Attrs, models.Attribute{ + Name: "RedrivePolicy", + Value: fmt.Sprintf(`{"maxReceiveCount":"100", "deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, af.QueueName), + }) + r3 := models.GetQueueAttributesResponse{} xml.Unmarshal([]byte(r), &r3) assert.Equal(t, exp3, r3) } @@ -348,7 +364,7 @@ func Test_CreateQueueV1_xml_no_attributes(t *testing.T) { Status(http.StatusOK). Body().Raw() - r3 := app.GetQueueAttributesResponse{} + r3 := models.GetQueueAttributesResponse{} xml.Unmarshal([]byte(r), &r3) assert.Equal(t, sf.BASE_GET_QUEUE_ATTRIBUTES_RESPONSE, r3) } @@ -383,17 +399,17 @@ func Test_CreateQueueV1_xml_with_attributes(t *testing.T) { r := e.POST("/"). WithForm(request). WithFormField("Attribute.1.Name", "VisibilityTimeout"). - WithFormField("Attribute.1.Value", "1"). + WithFormField("Attribute.1.Value", "5"). WithFormField("Attribute.2.Name", "MaximumMessageSize"). WithFormField("Attribute.2.Value", "2"). WithFormField("Attribute.3.Name", "DelaySeconds"). - WithFormField("Attribute.3.Value", "3"). + WithFormField("Attribute.3.Value", "1"). WithFormField("Attribute.4.Name", "MessageRetentionPeriod"). - WithFormField("Attribute.4.Value", "4"). + WithFormField("Attribute.4.Value", "3"). WithFormField("Attribute.5.Name", "Policy"). WithFormField("Attribute.5.Value", "{\"this-is\": \"the-policy\"}"). WithFormField("Attribute.6.Name", "ReceiveMessageWaitTimeSeconds"). - WithFormField("Attribute.6.Value", "5"). + WithFormField("Attribute.6.Value", "4"). WithFormField("Attribute.7.Name", "RedrivePolicy"). WithFormField("Attribute.7.Value", fmt.Sprintf("{\"maxReceiveCount\": 100, \"deadLetterTargetArn\":\"%s:new-queue-1\"}", af.BASE_ARN)). WithFormField("Attribute.8.Name", "RedriveAllowPolicy"). @@ -428,13 +444,18 @@ func Test_CreateQueueV1_xml_with_attributes(t *testing.T) { Body().Raw() dupe, _ := copystructure.Copy(sf.BASE_GET_QUEUE_ATTRIBUTES_RESPONSE) - exp3, _ := dupe.(app.GetQueueAttributesResponse) + exp3, _ := dupe.(models.GetQueueAttributesResponse) exp3.Result.Attrs[0].Value = "1" - exp3.Result.Attrs[1].Value = "3" - exp3.Result.Attrs[2].Value = "5" - exp3.Result.Attrs[7].Value = fmt.Sprintf("%s:new-queue-2", af.BASE_ARN) - exp3.Result.Attrs[8].Value = "{\"maxReceiveCount\": \"100\", \"deadLetterTargetArn\":\"new-queue-1\"}" - r3 := app.GetQueueAttributesResponse{} + exp3.Result.Attrs[1].Value = "2" + exp3.Result.Attrs[2].Value = "3" + exp3.Result.Attrs[3].Value = "4" + exp3.Result.Attrs[4].Value = "5" + exp3.Result.Attrs[9].Value = fmt.Sprintf("%s:new-queue-2", af.BASE_ARN) + exp3.Result.Attrs = append(exp3.Result.Attrs, models.Attribute{ + Name: "RedrivePolicy", + Value: fmt.Sprintf(`{"maxReceiveCount":"100", "deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, af.QueueName), + }) + r3 := models.GetQueueAttributesResponse{} xml.Unmarshal([]byte(r), &r3) assert.Equal(t, exp3, r3) } diff --git a/smoke_tests/sqs_get_queue_attributes_test.go b/smoke_tests/sqs_get_queue_attributes_test.go new file mode 100644 index 00000000..ad7298d3 --- /dev/null +++ b/smoke_tests/sqs_get_queue_attributes_test.go @@ -0,0 +1,335 @@ +package smoke_tests + +import ( + "context" + "encoding/xml" + "fmt" + "net/http" + "testing" + + "github.com/Admiral-Piett/goaws/app/models" + sf "github.com/Admiral-Piett/goaws/smoke_tests/fixtures" + "github.com/gavv/httpexpect/v2" + + "github.com/mitchellh/copystructure" + + af "github.com/Admiral-Piett/goaws/app/fixtures" + "github.com/aws/aws-sdk-go-v2/service/sqs/types" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/sqs" + + "github.com/Admiral-Piett/goaws/app/utils" + + "github.com/stretchr/testify/assert" +) + +func Test_GetQueueAttributes_json_all(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + utils.ResetResources() + }() + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + sqsClient := sqs.NewFromConfig(sdkConfig) + + redriveQueue := "redrive-queue" + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &redriveQueue, + }) + attributes := map[string]string{ + "DelaySeconds": "1", + "MaximumMessageSize": "2", + "MessageRetentionPeriod": "3", + //"Policy": "{\"this-is\": \"the-policy\"}", + "ReceiveMessageWaitTimeSeconds": "4", + "VisibilityTimeout": "5", + "RedrivePolicy": fmt.Sprintf(`{"maxReceiveCount":"100","deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, redriveQueue), + //"RedriveAllowPolicy": "{\"this-is\": \"the-redrive-allow-policy\"}", + } + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &af.QueueName, + Attributes: attributes, + }) + + queueUrl := fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName) + sdkResponse, err := sqsClient.GetQueueAttributes(context.TODO(), &sqs.GetQueueAttributesInput{ + QueueUrl: &queueUrl, + AttributeNames: []types.QueueAttributeName{"All"}, + }) + + dupe, _ := copystructure.Copy(attributes) + expectedAttributes, _ := dupe.(map[string]string) + expectedAttributes["RedrivePolicy"] = fmt.Sprintf(`{"maxReceiveCount":"100", "deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, redriveQueue) + expectedAttributes["ApproximateNumberOfMessages"] = "0" + expectedAttributes["ApproximateNumberOfMessagesNotVisible"] = "0" + expectedAttributes["CreatedTimestamp"] = "0000000000" + expectedAttributes["LastModifiedTimestamp"] = "0000000000" + expectedAttributes["QueueArn"] = fmt.Sprintf("%s:%s", af.BASE_ARN, af.QueueName) + assert.Nil(t, err) + assert.Equal(t, expectedAttributes, sdkResponse.Attributes) +} + +func Test_GetQueueAttributes_json_specific_attributes(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + utils.ResetResources() + }() + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + sqsClient := sqs.NewFromConfig(sdkConfig) + + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &af.QueueName, + Attributes: map[string]string{ + "DelaySeconds": "1", + "MaximumMessageSize": "2", + "MessageRetentionPeriod": "3", + }, + }) + + queueUrl := fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName) + sdkResponse, err := sqsClient.GetQueueAttributes(context.TODO(), &sqs.GetQueueAttributesInput{ + QueueUrl: &queueUrl, + AttributeNames: []types.QueueAttributeName{"DelaySeconds"}, + }) + + expectedAttributes := map[string]string{ + "DelaySeconds": "1", + } + + assert.Nil(t, err) + assert.Equal(t, expectedAttributes, sdkResponse.Attributes) +} + +func Test_GetQueueAttributes_json_missing_attribute_name_returns_all(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + utils.ResetResources() + }() + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + sqsClient := sqs.NewFromConfig(sdkConfig) + + redriveQueue := "redrive-queue" + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &redriveQueue, + }) + attributes := map[string]string{ + "DelaySeconds": "1", + "MaximumMessageSize": "2", + "MessageRetentionPeriod": "3", + //"Policy": "{\"this-is\": \"the-policy\"}", + "ReceiveMessageWaitTimeSeconds": "4", + "VisibilityTimeout": "5", + "RedrivePolicy": fmt.Sprintf(`{"maxReceiveCount":"100","deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, redriveQueue), + //"RedriveAllowPolicy": "{\"this-is\": \"the-redrive-allow-policy\"}", + } + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &af.QueueName, + Attributes: attributes, + }) + + queueUrl := fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName) + sdkResponse, err := sqsClient.GetQueueAttributes(context.TODO(), &sqs.GetQueueAttributesInput{ + QueueUrl: &queueUrl, + }) + + dupe, _ := copystructure.Copy(attributes) + expectedAttributes, _ := dupe.(map[string]string) + expectedAttributes["RedrivePolicy"] = fmt.Sprintf(`{"maxReceiveCount":"100", "deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, redriveQueue) + expectedAttributes["ApproximateNumberOfMessages"] = "0" + expectedAttributes["ApproximateNumberOfMessagesNotVisible"] = "0" + expectedAttributes["CreatedTimestamp"] = "0000000000" + expectedAttributes["LastModifiedTimestamp"] = "0000000000" + expectedAttributes["QueueArn"] = fmt.Sprintf("%s:%s", af.BASE_ARN, af.QueueName) + assert.Nil(t, err) + assert.Equal(t, expectedAttributes, sdkResponse.Attributes) +} + +func Test_GetQueueAttributes_xml_all(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + utils.ResetResources() + }() + + e := httpexpect.Default(t, server.URL) + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + sqsClient := sqs.NewFromConfig(sdkConfig) + + redriveQueue := "redrive-queue" + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &redriveQueue, + }) + attributes := map[string]string{ + "DelaySeconds": "1", + "MaximumMessageSize": "2", + "MessageRetentionPeriod": "3", + "ReceiveMessageWaitTimeSeconds": "4", + "VisibilityTimeout": "5", + "RedrivePolicy": fmt.Sprintf(`{"maxReceiveCount":"100","deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, redriveQueue), + } + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &af.QueueName, + Attributes: attributes, + }) + + r := e.POST("/"). + WithForm(sf.GetQueueAttributesRequestBodyXML). + WithFormField("AttributeName.1", "All"). + Expect(). + Status(http.StatusOK). + Body().Raw() + + dupe, _ := copystructure.Copy(sf.BASE_GET_QUEUE_ATTRIBUTES_RESPONSE) + expectedResponse, _ := dupe.(models.GetQueueAttributesResponse) + expectedResponse.Result.Attrs[0].Value = "1" + expectedResponse.Result.Attrs[1].Value = "2" + expectedResponse.Result.Attrs[2].Value = "3" + expectedResponse.Result.Attrs[3].Value = "4" + expectedResponse.Result.Attrs[4].Value = "5" + expectedResponse.Result.Attrs = append(expectedResponse.Result.Attrs, models.Attribute{ + Name: "RedrivePolicy", + Value: fmt.Sprintf(`{"maxReceiveCount":"100", "deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, redriveQueue), + }) + + r1 := models.GetQueueAttributesResponse{} + xml.Unmarshal([]byte(r), &r1) + assert.Equal(t, expectedResponse, r1) +} + +func Test_GetQueueAttributes_xml_select_attributes(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + utils.ResetResources() + }() + + e := httpexpect.Default(t, server.URL) + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + sqsClient := sqs.NewFromConfig(sdkConfig) + + attributes := map[string]string{ + "DelaySeconds": "1", + "MaximumMessageSize": "2", + "MessageRetentionPeriod": "3", + //"Policy": "{\"this-is\": \"the-policy\"}", + "ReceiveMessageWaitTimeSeconds": "4", + "VisibilityTimeout": "5", + //"RedriveAllowPolicy": "{\"this-is\": \"the-redrive-allow-policy\"}", + } + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &af.QueueName, + Attributes: attributes, + }) + + body := struct { + Action string `xml:"Action"` + Version string `xml:"Version"` + QueueUrl string `xml:"QueueUrl"` + }{ + Action: "GetQueueAttributes", + Version: "2012-11-05", + QueueUrl: fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName), + } + + r := e.POST("/"). + WithForm(body). + WithFormField("AttributeName.1", "DelaySeconds"). + Expect(). + Status(http.StatusOK). + Body().Raw() + + expectedResponse := models.GetQueueAttributesResponse{ + Xmlns: models.BASE_XMLNS, + Result: models.GetQueueAttributesResult{ + Attrs: []models.Attribute{ + { + Name: "DelaySeconds", + Value: "1", + }, + }, + }, + Metadata: models.BASE_RESPONSE_METADATA, + } + + r1 := models.GetQueueAttributesResponse{} + xml.Unmarshal([]byte(r), &r1) + assert.Equal(t, expectedResponse, r1) +} + +func Test_GetQueueAttributes_xml_missing_attribute_name_returns_all(t *testing.T) { + server := generateServer() + defer func() { + server.Close() + utils.ResetResources() + }() + + e := httpexpect.Default(t, server.URL) + + sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) + sdkConfig.BaseEndpoint = aws.String(server.URL) + sqsClient := sqs.NewFromConfig(sdkConfig) + + redriveQueue := "redrive-queue" + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &redriveQueue, + }) + attributes := map[string]string{ + "DelaySeconds": "1", + "MaximumMessageSize": "2", + "MessageRetentionPeriod": "3", + //"Policy": "{\"this-is\": \"the-policy\"}", + "ReceiveMessageWaitTimeSeconds": "4", + "VisibilityTimeout": "5", + "RedrivePolicy": fmt.Sprintf(`{"maxReceiveCount":"100","deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, redriveQueue), + } + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &af.QueueName, + Attributes: attributes, + }) + + body := struct { + Action string `xml:"Action"` + Version string `xml:"Version"` + QueueUrl string `xml:"QueueUrl"` + }{ + Action: "GetQueueAttributes", + Version: "2012-11-05", + QueueUrl: fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName), + } + + r := e.POST("/"). + WithForm(body). + Expect(). + Status(http.StatusOK). + Body().Raw() + + dupe, _ := copystructure.Copy(sf.BASE_GET_QUEUE_ATTRIBUTES_RESPONSE) + expectedResponse, _ := dupe.(models.GetQueueAttributesResponse) + expectedResponse.Result.Attrs[0].Value = "1" + expectedResponse.Result.Attrs[1].Value = "2" + expectedResponse.Result.Attrs[2].Value = "3" + expectedResponse.Result.Attrs[3].Value = "4" + expectedResponse.Result.Attrs[4].Value = "5" + expectedResponse.Result.Attrs = append(expectedResponse.Result.Attrs, models.Attribute{ + Name: "RedrivePolicy", + Value: fmt.Sprintf(`{"maxReceiveCount":"100", "deadLetterTargetArn":"%s:%s"}`, af.BASE_ARN, redriveQueue), + }) + + r1 := models.GetQueueAttributesResponse{} + xml.Unmarshal([]byte(r), &r1) + assert.Equal(t, expectedResponse, r1) +} diff --git a/smoke_tests/utils.go b/smoke_tests/utils.go index c755f546..22447c47 100644 --- a/smoke_tests/utils.go +++ b/smoke_tests/utils.go @@ -24,7 +24,7 @@ func generateServer() *httptest.Server { // USAGE: // // //sdkConfig, _ := config.LoadDefaultConfig(context.TODO()) -// sdkConfig := utils.GenerateLocalProxyConfig(9090) +// sdkConfig := GenerateLocalProxyConfig(9090) func GenerateLocalProxyConfig(proxyPort int) aws.Config { tr := &http.Transport{ TLSClientConfig: &tls.Config{