From b3d57628f914fd313abd87d74ea6e1f1f1534916 Mon Sep 17 00:00:00 2001 From: Devin Humphreys Date: Fri, 17 May 2024 17:50:58 -0400 Subject: [PATCH] Add SetQueueAttributesV1 for JSON support --- app/fixtures/sqs.go | 35 ++- app/gosqs/change_message_visibility.go | 2 +- app/gosqs/create_queue.go | 2 +- app/gosqs/create_queue_test.go | 38 +-- app/gosqs/delete_message.go | 2 +- app/gosqs/get_queue_attributes.go | 2 +- app/gosqs/get_queue_attributes_test.go | 14 +- app/gosqs/gosqs.go | 37 --- app/gosqs/gosqs_test.go | 34 --- app/gosqs/list_queues.go | 2 +- app/gosqs/list_queues_test.go | 12 +- app/gosqs/receive_message.go | 2 +- app/gosqs/receive_message_test.go | 7 +- app/gosqs/send_message.go | 2 +- app/gosqs/send_message_test.go | 12 +- app/gosqs/set_queue_attributes.go | 49 ++++ app/gosqs/set_queue_attributes_test.go | 168 +++++++++++++ app/models/models.go | 114 ++++++++- app/models/models_test.go | 142 +++++++++++ app/models/responses.go | 13 + app/router/router.go | 6 +- app/router/router_test.go | 2 +- app/utils/tests.go | 7 +- app/utils/utils.go | 6 +- app/utils/utils_test.go | 22 +- smoke_tests/fixtures/requests.go | 14 +- smoke_tests/sqs_get_queue_attributes_test.go | 13 +- smoke_tests/sqs_list_queues_test.go | 4 +- smoke_tests/sqs_set_queue_attributes_test.go | 243 +++++++++++++++++++ 29 files changed, 848 insertions(+), 158 deletions(-) create mode 100644 app/gosqs/set_queue_attributes.go create mode 100644 app/gosqs/set_queue_attributes_test.go create mode 100644 smoke_tests/sqs_set_queue_attributes_test.go diff --git a/app/fixtures/sqs.go b/app/fixtures/sqs.go index 6fbb9ec8..4ecaff43 100644 --- a/app/fixtures/sqs.go +++ b/app/fixtures/sqs.go @@ -2,20 +2,46 @@ package fixtures import ( "fmt" + "time" + + "github.com/Admiral-Piett/goaws/app" "github.com/Admiral-Piett/goaws/app/models" ) var QueueName = "new-queue-1" +var QueueUrl = fmt.Sprintf("%s/%s", BASE_URL, QueueName) var DeadLetterQueueName = "dead-letter-queue-1" +var FullyPopulatedQueue = &app.Queue{ + Name: QueueName, + URL: fmt.Sprintf("http://%s.%s:%s/%s/%s", + LOCAL_ENVIRONMENT.Region, + LOCAL_ENVIRONMENT.Host, + LOCAL_ENVIRONMENT.Port, + LOCAL_ENVIRONMENT.AccountID, + QueueName, + ), + Arn: fmt.Sprintf("arn:aws:sqs:%s:%s:%s", + LOCAL_ENVIRONMENT.Region, + LOCAL_ENVIRONMENT.AccountID, + QueueName, + ), + VisibilityTimeout: 5, + ReceiveMessageWaitTimeSeconds: 4, + DelaySeconds: 1, + MaximumMessageSize: 2, + MessageRetentionPeriod: 3, + Duplicates: make(map[string]time.Time), +} + var CreateQueueRequest = models.CreateQueueRequest{ QueueName: QueueName, - Attributes: CreateQueueAttributes, + Attributes: QueueAttributes, Tags: map[string]string{"my": "tag"}, } -var CreateQueueAttributes = models.Attributes{ +var QueueAttributes = models.Attributes{ DelaySeconds: 1, MaximumMessageSize: 2, MessageRetentionPeriod: 3, @@ -90,3 +116,8 @@ var GetQueueAttributesResponse = models.GetQueueAttributesResponse{ }}, Metadata: models.BASE_RESPONSE_METADATA, } + +var SetQueueAttributesRequest = models.SetQueueAttributesRequest{ + QueueUrl: fmt.Sprintf("%s/%s", BASE_URL, "unit-queue1"), + Attributes: QueueAttributes, +} diff --git a/app/gosqs/change_message_visibility.go b/app/gosqs/change_message_visibility.go index adf29a0d..8678cd4c 100644 --- a/app/gosqs/change_message_visibility.go +++ b/app/gosqs/change_message_visibility.go @@ -15,7 +15,7 @@ import ( func ChangeMessageVisibilityV1(req *http.Request) (int, interfaces.AbstractResponseBody) { requestBody := models.NewChangeMessageVisibilityRequest() - ok := utils.REQUEST_TRANSFORMER(requestBody, req) + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) if !ok { log.Error("Invalid Request - ChangeMessageVisibilityV1") return createErrorResponseV1(ErrInvalidParameterValue.Type) diff --git a/app/gosqs/create_queue.go b/app/gosqs/create_queue.go index aac0c5eb..f4c89d29 100644 --- a/app/gosqs/create_queue.go +++ b/app/gosqs/create_queue.go @@ -13,7 +13,7 @@ import ( func CreateQueueV1(req *http.Request) (int, interfaces.AbstractResponseBody) { requestBody := models.NewCreateQueueRequest() - ok := utils.REQUEST_TRANSFORMER(requestBody, req) + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) if !ok { log.Error("Invalid Request - CreateQueueV1") return createErrorResponseV1(ErrInvalidParameterValue.Type) diff --git a/app/gosqs/create_queue_test.go b/app/gosqs/create_queue_test.go index ecabef7a..65a3b805 100644 --- a/app/gosqs/create_queue_test.go +++ b/app/gosqs/create_queue_test.go @@ -22,34 +22,12 @@ func TestCreateQueueV1_success(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.CreateQueueRequest) *v = fixtures.CreateQueueRequest return true } - expectedQueue := &app.Queue{ - Name: fixtures.QueueName, - URL: fmt.Sprintf("http://%s.%s:%s/%s/%s", - fixtures.LOCAL_ENVIRONMENT.Region, - fixtures.LOCAL_ENVIRONMENT.Host, - fixtures.LOCAL_ENVIRONMENT.Port, - fixtures.LOCAL_ENVIRONMENT.AccountID, - fixtures.QueueName, - ), - Arn: fmt.Sprintf("arn:aws:sqs:%s:%s:%s", - fixtures.LOCAL_ENVIRONMENT.Region, - fixtures.LOCAL_ENVIRONMENT.AccountID, - fixtures.QueueName, - ), - VisibilityTimeout: 5, - ReceiveMessageWaitTimeSeconds: 4, - DelaySeconds: 1, - MaximumMessageSize: 2, - MessageRetentionPeriod: 3, - Duplicates: make(map[string]time.Time), - } - _, r := utils.GenerateRequestInfo("POST", "/", nil, true) code, response := CreateQueueV1(r) @@ -57,7 +35,7 @@ func TestCreateQueueV1_success(t *testing.T) { assert.Equal(t, fixtures.CreateQueueResponse, response) actualQueue := app.SyncQueues.Queues[fixtures.QueueName] - assert.Equal(t, expectedQueue, actualQueue) + assert.Equal(t, fixtures.FullyPopulatedQueue, actualQueue) } func TestCreateQueueV1_success_with_redrive_policy(t *testing.T) { @@ -67,7 +45,7 @@ func TestCreateQueueV1_success_with_redrive_policy(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { dupe, _ := copystructure.Copy(fixtures.CreateQueueRequest) c, _ := dupe.(models.CreateQueueRequest) c.Attributes.RedrivePolicy = models.RedrivePolicy{ @@ -126,7 +104,7 @@ func TestCreateQueueV1_success_with_existing_queue(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.CreateQueueRequest) *v = fixtures.CreateQueueRequest return true @@ -154,7 +132,7 @@ func TestCreateQueueV1_success_with_no_request_attributes_falls_back_to_default( utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { dupe, _ := copystructure.Copy(fixtures.CreateQueueRequest) c, _ := dupe.(models.CreateQueueRequest) c.Attributes = models.Attributes{} @@ -204,7 +182,7 @@ func TestCreateQueueV1_success_no_configured_region_for_queue_url(t *testing.T) utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { dupe, _ := copystructure.Copy(fixtures.CreateQueueRequest) c, _ := dupe.(models.CreateQueueRequest) c.Attributes = models.Attributes{} @@ -238,7 +216,7 @@ func TestCreateQueueV1_request_transformer_error(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { return false } @@ -255,7 +233,7 @@ func TestCreateQueueV1_invalid_dead_letter_queue_error(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { dupe, _ := copystructure.Copy(fixtures.CreateQueueRequest) c, _ := dupe.(models.CreateQueueRequest) c.Attributes.RedrivePolicy = models.RedrivePolicy{ diff --git a/app/gosqs/delete_message.go b/app/gosqs/delete_message.go index aab988e0..50de84d1 100644 --- a/app/gosqs/delete_message.go +++ b/app/gosqs/delete_message.go @@ -14,7 +14,7 @@ import ( func DeleteMessageV1(req *http.Request) (int, interfaces.AbstractResponseBody) { requestBody := models.NewDeleteMessageRequest() - ok := utils.REQUEST_TRANSFORMER(requestBody, req) + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) if !ok { log.Error("Invalid Request - DeleteMessageV1") return createErrorResponseV1(ErrInvalidParameterValue.Type) diff --git a/app/gosqs/get_queue_attributes.go b/app/gosqs/get_queue_attributes.go index 63b1d116..280c819a 100644 --- a/app/gosqs/get_queue_attributes.go +++ b/app/gosqs/get_queue_attributes.go @@ -18,7 +18,7 @@ import ( func GetQueueAttributesV1(req *http.Request) (int, interfaces.AbstractResponseBody) { requestBody := models.NewGetQueueAttributesRequest() - ok := utils.REQUEST_TRANSFORMER(requestBody, req) + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) if !ok { log.Error("Invalid Request - GetQueueAttributesV1") return createErrorResponseV1(ErrInvalidParameterValue.Type) diff --git a/app/gosqs/get_queue_attributes_test.go b/app/gosqs/get_queue_attributes_test.go index ec713551..79ecfe26 100644 --- a/app/gosqs/get_queue_attributes_test.go +++ b/app/gosqs/get_queue_attributes_test.go @@ -23,7 +23,7 @@ func TestGetQueueAttributesV1_success_all(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.GetQueueAttributesRequest) *v = fixtures.GetQueueAttributesRequest return true @@ -43,7 +43,7 @@ func TestGetQueueAttributesV1_success_no_request_attrs_returns_all(t *testing.T) utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.GetQueueAttributesRequest) *v = models.GetQueueAttributesRequest{ QueueUrl: "unit-queue1", @@ -65,7 +65,7 @@ func TestGetQueueAttributesV1_success_all_with_redrive_queue(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.GetQueueAttributesRequest) *v = models.GetQueueAttributesRequest{ QueueUrl: "unit-queue2", @@ -98,7 +98,7 @@ func TestGetQueueAttributesV1_success_specific_fields(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.GetQueueAttributesRequest) *v = models.GetQueueAttributesRequest{ QueueUrl: fmt.Sprintf("%s/unit-queue1", fixtures.BASE_URL), @@ -130,7 +130,7 @@ func TestGetQueueAttributesV1_request_transformer_error(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { return false } @@ -145,7 +145,7 @@ func TestGetQueueAttributesV1_missing_queue_url_in_request_returns_error(t *test utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.GetQueueAttributesRequest) *v = models.GetQueueAttributesRequest{ QueueUrl: "", @@ -165,7 +165,7 @@ func TestGetQueueAttributesV1_missing_queue_returns_error(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.GetQueueAttributesRequest) *v = fixtures.GetQueueAttributesRequest return true diff --git a/app/gosqs/gosqs.go b/app/gosqs/gosqs.go index 33ca4bbf..8a678bf7 100644 --- a/app/gosqs/gosqs.go +++ b/app/gosqs/gosqs.go @@ -415,43 +415,6 @@ func GetQueueUrl(w http.ResponseWriter, req *http.Request) { } } -func SetQueueAttributes(w http.ResponseWriter, req *http.Request) { - // Sent response type - w.Header().Set("Content-Type", "application/xml") - - queueUrl := getQueueFromPath(req.FormValue("QueueUrl"), req.URL.String()) - - queueName := "" - if queueUrl == "" { - vars := mux.Vars(req) - queueName = vars["queueName"] - } else { - uriSegments := strings.Split(queueUrl, "/") - queueName = uriSegments[len(uriSegments)-1] - } - - log.Println("Set Queue Attributes:", queueName) - app.SyncQueues.Lock() - if queue, ok := app.SyncQueues.Queues[queueName]; ok { - if err := validateAndSetQueueAttributesFromForm(queue, req.Form); err != nil { - createErrorResponse(w, req, err.Error()) - app.SyncQueues.Unlock() - return - } - - respStruct := app.SetQueueAttributesResponse{"http://queue.amazonaws.com/doc/2012-11-05/", 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.Unlock() -} - func getQueueFromPath(formVal string, theUrl string) string { if formVal != "" { return formVal diff --git a/app/gosqs/gosqs_test.go b/app/gosqs/gosqs_test.go index 3b205249..5957b1ba 100644 --- a/app/gosqs/gosqs_test.go +++ b/app/gosqs/gosqs_test.go @@ -600,40 +600,6 @@ func TestDeadLetterQueue(t *testing.T) { } } -func TestSetQueueAttributes_POST_QueueNotFound(t *testing.T) { - req, err := http.NewRequest("POST", "/", nil) - if err != nil { - t.Fatal(err) - } - - form := url.Values{} - form.Add("Action", "SetQueueAttributes") - form.Add("QueueUrl", "http://localhost:4100/queue/not-existing") - form.Add("Version", "2012-11-05") - req.PostForm = form - - // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. - rr := httptest.NewRecorder() - handler := http.HandlerFunc(SetQueueAttributes) - - // 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.StatusBadRequest { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusBadRequest) - } - - // Check the response body is what we expect. - expected := "NonExistentQueue" - if !strings.Contains(rr.Body.String(), expected) { - t.Errorf("handler returned unexpected body: got %v want %v", - rr.Body.String(), expected) - } -} - func TestSendingAndReceivingFromFIFOQueueReturnsSameMessageOnError(t *testing.T) { done := make(chan struct{}, 0) go PeriodicTasks(1*time.Second, done) diff --git a/app/gosqs/list_queues.go b/app/gosqs/list_queues.go index 35a84698..5336b30d 100644 --- a/app/gosqs/list_queues.go +++ b/app/gosqs/list_queues.go @@ -18,7 +18,7 @@ import ( // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ListQueues.html func ListQueuesV1(req *http.Request) (int, interfaces.AbstractResponseBody) { requestBody := models.NewListQueuesRequest() - ok := utils.REQUEST_TRANSFORMER(requestBody, req) + ok := utils.REQUEST_TRANSFORMER(requestBody, req, true) if !ok { log.Error("Invalid Request - ListQueuesV1") return createErrorResponseV1(ErrInvalidParameterValue.Type) diff --git a/app/gosqs/list_queues_test.go b/app/gosqs/list_queues_test.go index f1102dbd..30156265 100644 --- a/app/gosqs/list_queues_test.go +++ b/app/gosqs/list_queues_test.go @@ -20,7 +20,7 @@ func TestListQueuesV1_success(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.ListQueueRequest) *v = models.ListQueueRequest{} return true @@ -42,7 +42,7 @@ func TestListQueuesV1_success_no_queues(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.ListQueueRequest) *v = models.ListQueueRequest{} return true @@ -63,7 +63,7 @@ func TestListQueuesV1_success_with_queue_name_prefix(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.ListQueueRequest) *v = models.ListQueueRequest{QueueNamePrefix: "other"} return true @@ -84,7 +84,7 @@ func TestListQueuesV1_success_with_queue_name_prefix_no_matching_queues(t *testi utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.ListQueueRequest) *v = models.ListQueueRequest{QueueNamePrefix: "garbage"} return true @@ -99,13 +99,11 @@ func TestListQueuesV1_success_with_queue_name_prefix_no_matching_queues(t *testi } func TestListQueuesV1_request_transformer_error(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) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { return false } diff --git a/app/gosqs/receive_message.go b/app/gosqs/receive_message.go index 0464df3c..613ded75 100644 --- a/app/gosqs/receive_message.go +++ b/app/gosqs/receive_message.go @@ -17,7 +17,7 @@ import ( func ReceiveMessageV1(req *http.Request) (int, interfaces.AbstractResponseBody) { requestBody := models.NewReceiveMessageRequest() - ok := utils.REQUEST_TRANSFORMER(requestBody, req) + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) if !ok { log.Error("Invalid Request - ReceiveMessageV1") return createErrorResponseV1(ErrInvalidParameterValue.Type) diff --git a/app/gosqs/receive_message_test.go b/app/gosqs/receive_message_test.go index 12c6997c..ea43c57e 100644 --- a/app/gosqs/receive_message_test.go +++ b/app/gosqs/receive_message_test.go @@ -16,6 +16,8 @@ import ( "github.com/stretchr/testify/assert" ) +// TODO - figure out a better way to handle the wait time in these tests. Maybe in the smoke tests alone +// if there's nothing else? func TestReceiveMessageWaitTimeEnforcedV1(t *testing.T) { app.CurrentEnvironment = fixtures.LOCAL_ENVIRONMENT defer func() { @@ -25,6 +27,7 @@ func TestReceiveMessageWaitTimeEnforcedV1(t *testing.T) { q := &app.Queue{ Name: "waiting-queue", ReceiveMessageWaitTimeSeconds: 2, + //MaximumMessageSize: 262144, } app.SyncQueues.Queues["waiting-queue"] = q @@ -34,12 +37,12 @@ func TestReceiveMessageWaitTimeEnforcedV1(t *testing.T) { }, true) start := time.Now() - status, _ := ReceiveMessageV1(r) + status, response := ReceiveMessageV1(r) elapsed := time.Since(start) assert.Equal(t, http.StatusOK, status) if elapsed < 2*time.Second { - t.Fatal("handler didn't wait ReceiveMessageWaitTimeSeconds") + t.Fatalf("handler didn't wait ReceiveMessageWaitTimeSeconds %s", response) } // mock sending a message diff --git a/app/gosqs/send_message.go b/app/gosqs/send_message.go index 701eb636..a20931e5 100644 --- a/app/gosqs/send_message.go +++ b/app/gosqs/send_message.go @@ -19,7 +19,7 @@ import ( func SendMessageV1(req *http.Request) (int, interfaces.AbstractResponseBody) { requestBody := models.NewSendMessageRequest() - ok := utils.REQUEST_TRANSFORMER(requestBody, req) + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) if !ok { log.Error("Invalid Request - CreateQueueV1") return createErrorResponseV1(ErrInvalidParameterValue.Type) diff --git a/app/gosqs/send_message_test.go b/app/gosqs/send_message_test.go index a0fc1431..2c43ec6e 100644 --- a/app/gosqs/send_message_test.go +++ b/app/gosqs/send_message_test.go @@ -24,7 +24,7 @@ func TestSendMessageV1_Success(t *testing.T) { QueueUrl: "http://localhost:4200/new-queue-1", MessageBody: "Test Message", } - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.SendMessageRequest) *v = sendMessageRequest_success return true @@ -64,7 +64,7 @@ func TestSendMessageV1_Success_FIFOQueue(t *testing.T) { QueueUrl: "http://localhost:4200/new-queue-1", MessageBody: "Test Message", } - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.SendMessageRequest) *v = sendMessageRequest_success return true @@ -106,7 +106,7 @@ func TestSendMessageV1_Success_Deduplication(t *testing.T) { MessageBody: "Test Message", MessageDeduplicationId: "1", } - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.SendMessageRequest) *v = sendMessageRequest_success return true @@ -144,7 +144,7 @@ func TestSendMessageV1_request_transformer_error(t *testing.T) { utils.REQUEST_TRANSFORMER = utils.TransformRequest }() - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { return false } @@ -165,7 +165,7 @@ func TestSendMessageV1_MaximumMessageSize_MessageTooBig(t *testing.T) { QueueUrl: "http://localhost:4200/new-queue-1", MessageBody: "Test Message", } - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.SendMessageRequest) *v = sendMessageRequest_success return true @@ -198,7 +198,7 @@ func TestSendMessageV1_POST_QueueNonExistant(t *testing.T) { QueueUrl: "http://localhost:4200/new-queue-1", MessageBody: "Test Message", } - utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { + utils.REQUEST_TRANSFORMER = func(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { v := resultingStruct.(*models.SendMessageRequest) *v = sendMessageRequest_success return true diff --git a/app/gosqs/set_queue_attributes.go b/app/gosqs/set_queue_attributes.go new file mode 100644 index 00000000..f47876a6 --- /dev/null +++ b/app/gosqs/set_queue_attributes.go @@ -0,0 +1,49 @@ +package gosqs + +import ( + "net/http" + "strings" + + "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 SetQueueAttributesV1(req *http.Request) (int, interfaces.AbstractResponseBody) { + requestBody := models.NewSetQueueAttributesRequest() + ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) + if !ok { + log.Error("Invalid Request - GetQueueAttributesV1") + return createErrorResponseV1(ErrInvalidParameterValue.Type) + } + if requestBody.QueueUrl == "" { + log.Error("Missing QueueUrl - GetQueueAttributesV1") + return createErrorResponseV1(ErrInvalidParameterValue.Type) + } + + // NOTE: I tore out the handling for devining the url from a param. I can't find documentation that + // that is valid any longer. + uriSegments := strings.Split(requestBody.QueueUrl, "/") + queueName := uriSegments[len(uriSegments)-1] + + log.Infof("Set Queue Attributes: %s", queueName) + app.SyncQueues.Lock() + defer app.SyncQueues.Unlock() + queue, ok := app.SyncQueues.Queues[queueName] + if !ok { + log.Warningf("Get Queue URL: %s, queue does not exist!!!", queueName) + return createErrorResponseV1("QueueNotFound") + } + if err := setQueueAttributesV1(queue, requestBody.Attributes); err != nil { + return createErrorResponseV1(err.Error()) + } + + respStruct := models.SetQueueAttributesResponse{ + Xmlns: models.BASE_XMLNS, + Metadata: models.BASE_RESPONSE_METADATA, + } + return http.StatusOK, respStruct +} diff --git a/app/gosqs/set_queue_attributes_test.go b/app/gosqs/set_queue_attributes_test.go new file mode 100644 index 00000000..06a202ec --- /dev/null +++ b/app/gosqs/set_queue_attributes_test.go @@ -0,0 +1,168 @@ +package gosqs + +import ( + "fmt" + "net/http" + "testing" + + "github.com/Admiral-Piett/goaws/app" + "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 TestSetQueueAttributesV1_success_multiple_attributes(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, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetQueueAttributesRequest) + *v = fixtures.SetQueueAttributesRequest + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, response := SetQueueAttributesV1(r) + + expectedResponse := models.SetQueueAttributesResponse{ + Xmlns: models.BASE_XMLNS, + Metadata: models.BASE_RESPONSE_METADATA, + } + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, expectedResponse, response) + + actualQueue := app.SyncQueues.Queues["unit-queue1"] + assert.Equal(t, 5, actualQueue.VisibilityTimeout) + assert.Equal(t, 4, actualQueue.ReceiveMessageWaitTimeSeconds) + assert.Equal(t, 1, actualQueue.DelaySeconds) + assert.Equal(t, 2, actualQueue.MaximumMessageSize) + assert.Equal(t, 3, actualQueue.MessageRetentionPeriod) +} + +func TestSetQueueAttributesV1_success_single_attribute(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, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetQueueAttributesRequest) + *v = models.SetQueueAttributesRequest{ + QueueUrl: fmt.Sprintf("%s/%s", fixtures.BASE_URL, "unit-queue1"), + Attributes: models.Attributes{ + VisibilityTimeout: 5, + }, + } + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, response := SetQueueAttributesV1(r) + + expectedResponse := models.SetQueueAttributesResponse{ + Xmlns: models.BASE_XMLNS, + Metadata: models.BASE_RESPONSE_METADATA, + } + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, expectedResponse, response) + + actualQueue := app.SyncQueues.Queues["unit-queue1"] + assert.Equal(t, 5, actualQueue.VisibilityTimeout) + assert.Equal(t, 0, actualQueue.ReceiveMessageWaitTimeSeconds) + assert.Equal(t, 0, actualQueue.DelaySeconds) + assert.Equal(t, 0, actualQueue.MaximumMessageSize) + assert.Equal(t, 345600, actualQueue.MessageRetentionPeriod) +} + +func TestSetQueueAttributesV1_invalid_request_body(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, emptyRequestValid bool) (success bool) { + return false + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetQueueAttributesV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} + +func TestSetQueueAttributesV1_missing_queue_url(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, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetQueueAttributesRequest) + *v = models.SetQueueAttributesRequest{ + Attributes: models.Attributes{}, + } + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetQueueAttributesV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} + +func TestSetQueueAttributesV1_missing_expected_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, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetQueueAttributesRequest) + *v = models.SetQueueAttributesRequest{ + QueueUrl: "garbage", + } + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetQueueAttributesV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} + +func TestSetQueueAttributesV1_invalid_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, emptyRequestValid bool) (success bool) { + v := resultingStruct.(*models.SetQueueAttributesRequest) + *v = models.SetQueueAttributesRequest{ + QueueUrl: fmt.Sprintf("%s/%s", fixtures.BASE_URL, "unit-queue1"), + Attributes: models.Attributes{ + RedrivePolicy: models.RedrivePolicy{ + MaxReceiveCount: 100, + DeadLetterTargetArn: fmt.Sprintf("arn:aws:sqs:us-east-1:100010001000:%s", "garbage"), + }, + }, + } + return true + } + + _, r := utils.GenerateRequestInfo("POST", "/", nil, true) + code, _ := SetQueueAttributesV1(r) + + assert.Equal(t, http.StatusBadRequest, code) +} diff --git a/app/models/models.go b/app/models/models.go index 5c10832e..2f4ad18a 100644 --- a/app/models/models.go +++ b/app/models/models.go @@ -158,7 +158,6 @@ func (r *ListQueueRequest) SetAttributesFromForm(values url.Values) { r.QueueNamePrefix = values.Get("QueueNamePrefix") } -// TODO - test models and responses func NewGetQueueAttributesRequest() *GetQueueAttributesRequest { return &GetQueueAttributesRequest{} } @@ -182,7 +181,6 @@ func (r *GetQueueAttributesRequest) SetAttributesFromForm(values url.Values) { } /*** Send Message Request */ - func NewSendMessageRequest() *SendMessageRequest { return &SendMessageRequest{ MessageAttributes: make(map[string]MessageAttributeValue), @@ -244,6 +242,118 @@ func (r *SendMessageRequest) SetAttributesFromForm(values url.Values) { } } +func NewSetQueueAttributesRequest() *SetQueueAttributesRequest { + return &SetQueueAttributesRequest{} +} + +type SetQueueAttributesRequest struct { + QueueUrl string `json:"QueueUrl"` + Attributes Attributes `json:"Attributes"` +} + +func (r *SetQueueAttributesRequest) SetAttributesFromForm(values url.Values) { + r.QueueUrl = values.Get("QueueUrl") + // TODO - could we share with CreateQueueRequest? + for i := 1; true; i++ { + nameKey := fmt.Sprintf("Attribute.%d.Name", i) + attrName := values.Get(nameKey) + if attrName == "" { + break + } + + valueKey := fmt.Sprintf("Attribute.%d.Value", i) + attrValue := values.Get(valueKey) + if attrValue == "" { + continue + } + switch attrName { + case "DelaySeconds": + tmp, err := strconv.Atoi(attrValue) + if err != nil { + log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue) + continue + } + r.Attributes.DelaySeconds = StringToInt(tmp) + case "MaximumMessageSize": + tmp, err := strconv.Atoi(attrValue) + if err != nil { + log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue) + continue + } + r.Attributes.MaximumMessageSize = StringToInt(tmp) + case "MessageRetentionPeriod": + tmp, err := strconv.Atoi(attrValue) + if err != nil { + log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue) + continue + } + r.Attributes.MessageRetentionPeriod = StringToInt(tmp) + case "Policy": + var tmp map[string]interface{} + err := json.Unmarshal([]byte(attrValue), &tmp) + if err != nil { + log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue) + continue + } + r.Attributes.Policy = tmp + case "ReceiveMessageWaitTimeSeconds": + tmp, err := strconv.Atoi(attrValue) + if err != nil { + log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue) + continue + } + r.Attributes.ReceiveMessageWaitTimeSeconds = StringToInt(tmp) + case "VisibilityTimeout": + tmp, err := strconv.Atoi(attrValue) + if err != nil { + log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue) + continue + } + r.Attributes.VisibilityTimeout = StringToInt(tmp) + case "RedrivePolicy": + tmp := RedrivePolicy{} + var decodedPolicy struct { + MaxReceiveCount interface{} `json:"maxReceiveCount"` + DeadLetterTargetArn string `json:"deadLetterTargetArn"` + } + err := json.Unmarshal([]byte(attrValue), &decodedPolicy) + if err != nil || decodedPolicy.DeadLetterTargetArn == "" { + log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue) + continue + } + // Support both int and string types (historic processing), set a default of 10 if not provided. + // Go will default into float64 for interface{} types when parsing numbers + receiveCount, ok := decodedPolicy.MaxReceiveCount.(float64) + if !ok { + receiveCount = 10 + t, ok := decodedPolicy.MaxReceiveCount.(string) + if ok { + r, err := strconv.ParseFloat(t, 64) + if err == nil { + receiveCount = r + } else { + log.Debugf("Failed to parse form attribute (maxReceiveCount) - %s: %s", attrName, attrValue) + } + } else { + log.Debugf("Failed to parse form attribute (maxReceiveCount) - %s: %s", attrName, attrValue) + } + } + tmp.MaxReceiveCount = StringToInt(receiveCount) + tmp.DeadLetterTargetArn = decodedPolicy.DeadLetterTargetArn + r.Attributes.RedrivePolicy = tmp + case "RedriveAllowPolicy": + var tmp map[string]interface{} + err := json.Unmarshal([]byte(attrValue), &tmp) + if err != nil { + log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue) + continue + } + r.Attributes.RedriveAllowPolicy = tmp + } + } + return +} + // 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 9df80860..97eded66 100644 --- a/app/models/models_test.go +++ b/app/models/models_test.go @@ -319,3 +319,145 @@ func TestSendMessageRequest_SetAttributesFromForm_success(t *testing.T) { assert.Equal(t, "", attr2.StringValue) assert.Equal(t, "VmFsdWUy", attr2.BinaryValue) } + +func TestSetQueueAttributesRequest_SetAttributesFromForm_success(t *testing.T) { + expectedRedrivePolicy := RedrivePolicy{ + MaxReceiveCount: 100, + DeadLetterTargetArn: "dead-letter-queue-arn", + } + + form := url.Values{} + form.Add("Action", "CreateQueue") + form.Add("QueueName", "new-queue") + form.Add("Version", "2012-11-05") + form.Add("Attribute.1.Name", "DelaySeconds") + form.Add("Attribute.1.Value", "1") + form.Add("Attribute.2.Name", "MaximumMessageSize") + form.Add("Attribute.2.Value", "2") + form.Add("Attribute.3.Name", "MessageRetentionPeriod") + form.Add("Attribute.3.Value", "3") + form.Add("Attribute.4.Name", "Policy") + form.Add("Attribute.4.Value", "{\"i-am\":\"the-policy\"}") + form.Add("Attribute.5.Name", "ReceiveMessageWaitTimeSeconds") + form.Add("Attribute.5.Value", "4") + form.Add("Attribute.6.Name", "VisibilityTimeout") + form.Add("Attribute.6.Value", "5") + form.Add("Attribute.7.Name", "RedrivePolicy") + form.Add("Attribute.7.Value", "{\"maxReceiveCount\": 100, \"deadLetterTargetArn\":\"dead-letter-queue-arn\"}") + form.Add("Attribute.8.Name", "RedriveAllowPolicy") + form.Add("Attribute.8.Value", "{\"i-am\":\"the-redrive-allow-policy\"}") + + cqr := &SetQueueAttributesRequest{ + Attributes: Attributes{ + DelaySeconds: 1, + MaximumMessageSize: 262144, + MessageRetentionPeriod: 345600, + ReceiveMessageWaitTimeSeconds: 10, + VisibilityTimeout: 30, + }, + } + cqr.SetAttributesFromForm(form) + + assert.Equal(t, StringToInt(1), cqr.Attributes.DelaySeconds) + assert.Equal(t, StringToInt(2), cqr.Attributes.MaximumMessageSize) + assert.Equal(t, StringToInt(3), cqr.Attributes.MessageRetentionPeriod) + assert.Equal(t, map[string]interface{}{"i-am": "the-policy"}, cqr.Attributes.Policy) + assert.Equal(t, StringToInt(4), cqr.Attributes.ReceiveMessageWaitTimeSeconds) + assert.Equal(t, StringToInt(5), cqr.Attributes.VisibilityTimeout) + assert.Equal(t, expectedRedrivePolicy, cqr.Attributes.RedrivePolicy) + assert.Equal(t, map[string]interface{}{"i-am": "the-redrive-allow-policy"}, cqr.Attributes.RedriveAllowPolicy) +} + +func TestSetQueueAttributesRequest_SetAttributesFromForm_success_handles_redrive_recieve_count_int(t *testing.T) { + expectedRedrivePolicy := RedrivePolicy{ + MaxReceiveCount: 100, + DeadLetterTargetArn: "dead-letter-queue-arn", + } + + form := url.Values{} + form.Add("Attribute.1.Name", "RedrivePolicy") + form.Add("Attribute.1.Value", "{\"maxReceiveCount\": 100, \"deadLetterTargetArn\":\"dead-letter-queue-arn\"}") + + cqr := &SetQueueAttributesRequest{ + Attributes: Attributes{}, + } + cqr.SetAttributesFromForm(form) + + assert.Equal(t, expectedRedrivePolicy, cqr.Attributes.RedrivePolicy) +} + +func TestSetQueueAttributesRequest_SetAttributesFromForm_success_handles_redrive_recieve_count_string(t *testing.T) { + expectedRedrivePolicy := RedrivePolicy{ + MaxReceiveCount: 100, + DeadLetterTargetArn: "dead-letter-queue-arn", + } + + form := url.Values{} + form.Add("Attribute.1.Name", "RedrivePolicy") + form.Add("Attribute.1.Value", "{\"maxReceiveCount\": \"100\", \"deadLetterTargetArn\":\"dead-letter-queue-arn\"}") + + cqr := &SetQueueAttributesRequest{ + Attributes: Attributes{}, + } + cqr.SetAttributesFromForm(form) + + assert.Equal(t, expectedRedrivePolicy, cqr.Attributes.RedrivePolicy) +} + +func TestSetQueueAttributesRequest_SetAttributesFromForm_success_default_unparsable_redrive_recieve_count(t *testing.T) { + defaultRedrivePolicy := RedrivePolicy{ + MaxReceiveCount: 10, + DeadLetterTargetArn: "dead-letter-queue-arn", + } + + form := url.Values{} + form.Add("Attribute.1.Name", "RedrivePolicy") + form.Add("Attribute.1.Value", "{\"maxReceiveCount\": null, \"deadLetterTargetArn\":\"dead-letter-queue-arn\"}") + + cqr := &SetQueueAttributesRequest{ + Attributes: Attributes{}, + } + cqr.SetAttributesFromForm(form) + + assert.Equal(t, defaultRedrivePolicy, cqr.Attributes.RedrivePolicy) +} + +func TestSetQueueAttributesRequest_SetAttributesFromForm_success_skips_invalid_values(t *testing.T) { + form := url.Values{} + form.Add("Attribute.1.Name", "DelaySeconds") + form.Add("Attribute.1.Value", "garbage") + form.Add("Attribute.2.Name", "MaximumMessageSize") + form.Add("Attribute.2.Value", "garbage") + form.Add("Attribute.3.Name", "MessageRetentionPeriod") + form.Add("Attribute.3.Value", "garbage") + form.Add("Attribute.4.Name", "Policy") + form.Add("Attribute.4.Value", "garbage") + form.Add("Attribute.5.Name", "ReceiveMessageWaitTimeSeconds") + form.Add("Attribute.5.Value", "garbage") + form.Add("Attribute.6.Name", "VisibilityTimeout") + form.Add("Attribute.6.Value", "garbage") + form.Add("Attribute.7.Name", "RedrivePolicy") + form.Add("Attribute.7.Value", "garbage") + form.Add("Attribute.8.Name", "RedriveAllowPolicy") + form.Add("Attribute.8.Value", "garbage") + + cqr := &SetQueueAttributesRequest{ + Attributes: Attributes{ + DelaySeconds: 1, + MaximumMessageSize: 262144, + MessageRetentionPeriod: 345600, + ReceiveMessageWaitTimeSeconds: 10, + VisibilityTimeout: 30, + }, + } + cqr.SetAttributesFromForm(form) + + assert.Equal(t, StringToInt(1), cqr.Attributes.DelaySeconds) + assert.Equal(t, StringToInt(262144), cqr.Attributes.MaximumMessageSize) + assert.Equal(t, StringToInt(345600), cqr.Attributes.MessageRetentionPeriod) + assert.Equal(t, map[string]interface{}(nil), cqr.Attributes.Policy) + assert.Equal(t, StringToInt(10), cqr.Attributes.ReceiveMessageWaitTimeSeconds) + assert.Equal(t, StringToInt(30), cqr.Attributes.VisibilityTimeout) + assert.Equal(t, RedrivePolicy{}, cqr.Attributes.RedrivePolicy) + assert.Equal(t, map[string]interface{}(nil), cqr.Attributes.RedriveAllowPolicy) +} diff --git a/app/models/responses.go b/app/models/responses.go index 133357f7..38bd3e5f 100644 --- a/app/models/responses.go +++ b/app/models/responses.go @@ -223,3 +223,16 @@ func (r DeleteMessageResponse) GetResult() interface{} { func (r DeleteMessageResponse) GetRequestId() string { return r.Metadata.RequestId } + +type SetQueueAttributesResponse struct { + Xmlns string `xml:"xmlns,attr,omitempty"` + Metadata app.ResponseMetadata `xml:"ResponseMetadata,omitempty"` +} + +func (r SetQueueAttributesResponse) GetResult() interface{} { + return nil +} + +func (r SetQueueAttributesResponse) GetRequestId() string { + return r.Metadata.RequestId +} diff --git a/app/router/router.go b/app/router/router.go index 482ed0b9..4eac5bd4 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -41,7 +41,9 @@ func encodeResponse(w http.ResponseWriter, req *http.Request, statusCode int, bo // Stupidly these `WriteHeader` calls have to be here, if they're at the start // they lock the headers, at the end they're ignored. w.WriteHeader(statusCode) - + if body.GetResult() == nil { + return + } err := json.NewEncoder(w).Encode(body.GetResult()) if err != nil { log.Errorf("Response Encoding Error: %v\nResponse: %+v", err, body) @@ -64,6 +66,7 @@ var routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractR "CreateQueue": sqs.CreateQueueV1, "ListQueues": sqs.ListQueuesV1, "GetQueueAttributes": sqs.GetQueueAttributesV1, + "SetQueueAttributes": sqs.SetQueueAttributesV1, "SendMessage": sqs.SendMessageV1, "ReceiveMessage": sqs.ReceiveMessageV1, "ChangeMessageVisibility": sqs.ChangeMessageVisibilityV1, @@ -72,7 +75,6 @@ var routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractR var routingTable = map[string]http.HandlerFunc{ // SQS - "SetQueueAttributes": sqs.SetQueueAttributes, "SendMessageBatch": sqs.SendMessageBatch, "DeleteMessageBatch": sqs.DeleteMessageBatch, "GetQueueUrl": sqs.GetQueueUrl, diff --git a/app/router/router_test.go b/app/router/router_test.go index dbc66773..e703bae8 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -263,6 +263,7 @@ func TestActionHandler_v0_xml(t *testing.T) { "CreateQueue": sqs.CreateQueueV1, "ListQueues": sqs.ListQueuesV1, "GetQueueAttributes": sqs.GetQueueAttributesV1, + "SetQueueAttributes": sqs.SetQueueAttributesV1, "SendMessage": sqs.SendMessageV1, "ReceiveMessage": sqs.ReceiveMessageV1, "DeleteMessage": sqs.DeleteMessageV1, @@ -270,7 +271,6 @@ func TestActionHandler_v0_xml(t *testing.T) { } routingTable = map[string]http.HandlerFunc{ // SQS - "SetQueueAttributes": sqs.SetQueueAttributes, "SendMessageBatch": sqs.SendMessageBatch, "DeleteMessageBatch": sqs.DeleteMessageBatch, "GetQueueUrl": sqs.GetQueueUrl, diff --git a/app/utils/tests.go b/app/utils/tests.go index bac67374..3be85742 100644 --- a/app/utils/tests.go +++ b/app/utils/tests.go @@ -35,12 +35,13 @@ func GenerateRequestInfo(method, url string, body interface{}, isJson bool) (*ht var req *http.Request var err error if isJson { - if body != nil { + // Default request body when none is provided + if body == nil { + req, err = http.NewRequest(method, url, http.NoBody) + } else { b, _ := json.Marshal(body) request_body := bytes.NewBuffer(b) req, err = http.NewRequest(method, url, request_body) - } else { - req, err = http.NewRequest(method, url, nil) } if err != nil { panic(err) diff --git a/app/utils/utils.go b/app/utils/utils.go index 3e1aa5fd..d2030b9e 100644 --- a/app/utils/utils.go +++ b/app/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "encoding/json" "fmt" + "io" "net/http" "net/url" @@ -24,13 +25,16 @@ func InitializeDecoders() { // QUESTION - alternately we could have the router.actionHandler method call this, but then our router maps // need to track the request type AND the function call. I think there'd be a lot of interface switching // back and forth. -func TransformRequest(resultingStruct interfaces.AbstractRequestBody, req *http.Request) (success bool) { +func TransformRequest(resultingStruct interfaces.AbstractRequestBody, req *http.Request, emptyRequestValid bool) (success bool) { switch req.Header.Get("Content-Type") { case "application/x-amz-json-1.0": //Read body data to parse json decoder := json.NewDecoder(req.Body) err := decoder.Decode(resultingStruct) if err != nil { + if emptyRequestValid && err == io.EOF { + return true + } log.Debugf("TransformRequest Failure - %s", err.Error()) return false } diff --git a/app/utils/utils_test.go b/app/utils/utils_test.go index ea35f45f..418761cd 100644 --- a/app/utils/utils_test.go +++ b/app/utils/utils_test.go @@ -20,13 +20,25 @@ func TestTransformRequest_success_json(t *testing.T) { mock := &mocks.MockRequestBody{} - ok := TransformRequest(mock, r) + ok := TransformRequest(mock, r, false) assert.True(t, ok) assert.Equal(t, "mock-value", mock.RequestFieldStr) assert.False(t, mock.SetAttributesFromFormCalled) } +func TestTransformRequest_success_json_empty_request_accepted(t *testing.T) { + _, r := GenerateRequestInfo("POST", "url", nil, true) + + mock := &mocks.MockRequestBody{} + + ok := TransformRequest(mock, r, true) + + assert.True(t, ok) + //assert.Equal(t, "mock-value", mock.RequestFieldStr) + assert.False(t, mock.SetAttributesFromFormCalled) +} + func TestTransformRequest_success_xml(t *testing.T) { _, r := GenerateRequestInfo("POST", "url", nil, false) form := url.Values{} @@ -40,7 +52,7 @@ func TestTransformRequest_success_xml(t *testing.T) { mock := &mocks.MockRequestBody{} - ok := TransformRequest(mock, r) + ok := TransformRequest(mock, r, false) assert.True(t, ok) assert.True(t, mock.SetAttributesFromFormCalled) @@ -52,7 +64,7 @@ func TestTransformRequest_error_invalid_request_body_json(t *testing.T) { mock := &mocks.MockRequestBody{} - ok := TransformRequest(mock, r) + ok := TransformRequest(mock, r, false) assert.False(t, ok) assert.Equal(t, "", mock.RequestFieldStr) @@ -64,7 +76,7 @@ func TestTransformRequest_error_failure_to_parse_form_xml(t *testing.T) { mock := &mocks.MockRequestBody{} - ok := TransformRequest(mock, r) + ok := TransformRequest(mock, r, false) assert.False(t, ok) assert.False(t, mock.SetAttributesFromFormCalled) @@ -79,7 +91,7 @@ func TestTransformRequest_error_invalid_request_body_xml(t *testing.T) { mock := &mocks.MockRequestBody{} - ok := TransformRequest(mock, r) + ok := TransformRequest(mock, r, false) assert.False(t, ok) assert.False(t, mock.SetAttributesFromFormCalled) diff --git a/smoke_tests/fixtures/requests.go b/smoke_tests/fixtures/requests.go index 14f3c7be..39cf15be 100644 --- a/smoke_tests/fixtures/requests.go +++ b/smoke_tests/fixtures/requests.go @@ -24,13 +24,23 @@ var GetQueueAttributesRequestBodyXML = struct { Action: "GetQueueAttributes", Version: "2012-11-05", Attribute1: "All", - QueueUrl: fmt.Sprintf("%s/new-queue-1", af.BASE_URL), + QueueUrl: af.QueueUrl, +} + +var SetQueueAttributesRequestBodyXML = struct { + Action string `xml:"Action"` + Version string `xml:"Version"` + QueueUrl string `xml:"QueueUrl"` +}{ + Action: "SetQueueAttributes", + Version: "2012-11-05", + QueueUrl: af.QueueUrl, } var CreateQueueV1RequestBodyJSON = models.CreateQueueRequest{ QueueName: af.QueueName, Version: "2012-11-05", - Attributes: af.CreateQueueAttributes, + Attributes: af.QueueAttributes, } var CreateQueueV1RequestXML_NoAttributes = struct { diff --git a/smoke_tests/sqs_get_queue_attributes_test.go b/smoke_tests/sqs_get_queue_attributes_test.go index ad7298d3..3a1a8576 100644 --- a/smoke_tests/sqs_get_queue_attributes_test.go +++ b/smoke_tests/sqs_get_queue_attributes_test.go @@ -55,9 +55,8 @@ func Test_GetQueueAttributes_json_all(t *testing.T) { Attributes: attributes, }) - queueUrl := fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName) sdkResponse, err := sqsClient.GetQueueAttributes(context.TODO(), &sqs.GetQueueAttributesInput{ - QueueUrl: &queueUrl, + QueueUrl: &af.QueueUrl, AttributeNames: []types.QueueAttributeName{"All"}, }) @@ -93,9 +92,8 @@ func Test_GetQueueAttributes_json_specific_attributes(t *testing.T) { }, }) - queueUrl := fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName) sdkResponse, err := sqsClient.GetQueueAttributes(context.TODO(), &sqs.GetQueueAttributesInput{ - QueueUrl: &queueUrl, + QueueUrl: &af.QueueUrl, AttributeNames: []types.QueueAttributeName{"DelaySeconds"}, }) @@ -137,9 +135,8 @@ func Test_GetQueueAttributes_json_missing_attribute_name_returns_all(t *testing. Attributes: attributes, }) - queueUrl := fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName) sdkResponse, err := sqsClient.GetQueueAttributes(context.TODO(), &sqs.GetQueueAttributesInput{ - QueueUrl: &queueUrl, + QueueUrl: &af.QueueUrl, }) dupe, _ := copystructure.Copy(attributes) @@ -242,7 +239,7 @@ func Test_GetQueueAttributes_xml_select_attributes(t *testing.T) { }{ Action: "GetQueueAttributes", Version: "2012-11-05", - QueueUrl: fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName), + QueueUrl: af.QueueUrl, } r := e.POST("/"). @@ -308,7 +305,7 @@ func Test_GetQueueAttributes_xml_missing_attribute_name_returns_all(t *testing.T }{ Action: "GetQueueAttributes", Version: "2012-11-05", - QueueUrl: fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName), + QueueUrl: af.QueueUrl, } r := e.POST("/"). diff --git a/smoke_tests/sqs_list_queues_test.go b/smoke_tests/sqs_list_queues_test.go index 58fd44b2..a33c56db 100644 --- a/smoke_tests/sqs_list_queues_test.go +++ b/smoke_tests/sqs_list_queues_test.go @@ -67,7 +67,7 @@ func Test_ListQueues_json_multiple_queues(t *testing.T) { assert.Nil(t, err) - assert.Contains(t, sdkResponse.QueueUrls, fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName)) + assert.Contains(t, sdkResponse.QueueUrls, af.QueueUrl) assert.Contains(t, sdkResponse.QueueUrls, fmt.Sprintf("%s/new-queue-2", af.BASE_URL)) assert.Contains(t, sdkResponse.QueueUrls, fmt.Sprintf("%s/new-queue-3", af.BASE_URL)) } @@ -164,7 +164,7 @@ func Test_ListQueues_xml_multiple_queues(t *testing.T) { response := models.ListQueuesResponse{} xml.Unmarshal([]byte(r), &response) - assert.Contains(t, response.Result.QueueUrls, fmt.Sprintf("%s/%s", af.BASE_URL, af.QueueName)) + assert.Contains(t, response.Result.QueueUrls, af.QueueUrl) assert.Contains(t, response.Result.QueueUrls, fmt.Sprintf("%s/new-queue-2", af.BASE_URL)) assert.Contains(t, response.Result.QueueUrls, fmt.Sprintf("%s/new-queue-3", af.BASE_URL)) } diff --git a/smoke_tests/sqs_set_queue_attributes_test.go b/smoke_tests/sqs_set_queue_attributes_test.go new file mode 100644 index 00000000..513a1dfd --- /dev/null +++ b/smoke_tests/sqs_set_queue_attributes_test.go @@ -0,0 +1,243 @@ +package smoke_tests + +import ( + "context" + "fmt" + "net/http" + "testing" + + 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_SetQueueAttributes_json_multiple(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, + }) + queueName := "unit-queue1" + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &queueName, + }) + 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\"}", + } + + queueUrl := fmt.Sprintf("%s/%s", af.BASE_URL, queueName) + _, err := sqsClient.SetQueueAttributes(context.TODO(), &sqs.SetQueueAttributesInput{ + QueueUrl: &queueUrl, + Attributes: attributes, + }) + + assert.Nil(t, err) + + 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, queueName) + assert.Nil(t, err) + assert.Equal(t, expectedAttributes, sdkResponse.Attributes) +} + +func Test_SetQueueAttributes_json_single(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) + + queueName := "unit-queue1" + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &queueName, + }) + attributes := map[string]string{ + "DelaySeconds": "1", + } + + queueUrl := fmt.Sprintf("%s/%s", af.BASE_URL, queueName) + _, err := sqsClient.SetQueueAttributes(context.TODO(), &sqs.SetQueueAttributesInput{ + QueueUrl: &queueUrl, + Attributes: attributes, + }) + + assert.Nil(t, err) + + sdkResponse, err := sqsClient.GetQueueAttributes(context.TODO(), &sqs.GetQueueAttributesInput{ + QueueUrl: &queueUrl, + AttributeNames: []types.QueueAttributeName{"All"}, + }) + + dupe, _ := copystructure.Copy(attributes) + expectedAttributes, _ := dupe.(map[string]string) + expectedAttributes["ReceiveMessageWaitTimeSeconds"] = "0" + expectedAttributes["VisibilityTimeout"] = "0" + expectedAttributes["MaximumMessageSize"] = "0" + expectedAttributes["MessageRetentionPeriod"] = "0" + expectedAttributes["ApproximateNumberOfMessages"] = "0" + expectedAttributes["ApproximateNumberOfMessagesNotVisible"] = "0" + expectedAttributes["CreatedTimestamp"] = "0000000000" + expectedAttributes["LastModifiedTimestamp"] = "0000000000" + expectedAttributes["QueueArn"] = fmt.Sprintf("%s:%s", af.BASE_ARN, queueName) + assert.Nil(t, err) + assert.Equal(t, expectedAttributes, sdkResponse.Attributes) +} + +func Test_SetQueueAttributes_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, + }) + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &af.QueueName, + }) + + e.POST("/"). + WithForm(sf.SetQueueAttributesRequestBodyXML). + WithFormField("Attribute.1.Name", "VisibilityTimeout"). + WithFormField("Attribute.1.Value", "5"). + WithFormField("Attribute.2.Name", "MaximumMessageSize"). + WithFormField("Attribute.2.Value", "2"). + WithFormField("Attribute.3.Name", "DelaySeconds"). + WithFormField("Attribute.3.Value", "1"). + WithFormField("Attribute.4.Name", "MessageRetentionPeriod"). + 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", "4"). + WithFormField("Attribute.7.Name", "RedrivePolicy"). + WithFormField("Attribute.7.Value", fmt.Sprintf("{\"maxReceiveCount\": 100, \"deadLetterTargetArn\":\"%s:%s\"}", af.BASE_ARN, redriveQueue)). + WithFormField("Attribute.8.Name", "RedriveAllowPolicy"). + WithFormField("Attribute.8.Value", "{\"this-is\": \"the-redrive-allow-policy\"}"). + Expect(). + Status(http.StatusOK). + Body().Raw() + + sdkResponse, err := sqsClient.GetQueueAttributes(context.TODO(), &sqs.GetQueueAttributesInput{ + QueueUrl: &af.QueueUrl, + AttributeNames: []types.QueueAttributeName{"All"}, + }) + + expectedAttributes := 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\"}", + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "0000000000", + "LastModifiedTimestamp": "0000000000", + "QueueArn": fmt.Sprintf("%s:%s", af.BASE_ARN, af.QueueName), + } + assert.Nil(t, err) + assert.Equal(t, expectedAttributes, sdkResponse.Attributes) +} + +func Test_SetQueueAttributes_xml_single(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, + }) + sqsClient.CreateQueue(context.TODO(), &sqs.CreateQueueInput{ + QueueName: &af.QueueName, + }) + + e.POST("/"). + WithForm(sf.SetQueueAttributesRequestBodyXML). + WithFormField("Attribute.1.Name", "VisibilityTimeout"). + WithFormField("Attribute.1.Value", "5"). + Expect(). + Status(http.StatusOK). + Body().Raw() + + sdkResponse, err := sqsClient.GetQueueAttributes(context.TODO(), &sqs.GetQueueAttributesInput{ + QueueUrl: &af.QueueUrl, + AttributeNames: []types.QueueAttributeName{"All"}, + }) + + expectedAttributes := map[string]string{ + "DelaySeconds": "0", + "MaximumMessageSize": "0", + "MessageRetentionPeriod": "0", + "ReceiveMessageWaitTimeSeconds": "0", + "VisibilityTimeout": "5", + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "0000000000", + "LastModifiedTimestamp": "0000000000", + "QueueArn": fmt.Sprintf("%s:%s", af.BASE_ARN, af.QueueName), + } + assert.Nil(t, err) + assert.Equal(t, expectedAttributes, sdkResponse.Attributes) +}