fix: v0.1.19 — валидация VisibilityTimeout/WaitTimeSeconds/пустой MessageBody, fix hardcore_test LONG_Q length

This commit is contained in:
Naeel
2026-04-11 15:56:31 +03:00
parent 95d1dc1adb
commit d633e59a50
5 changed files with 90 additions and 76 deletions
+71 -70
View File
@@ -3,85 +3,86 @@
package gosqs package gosqs
import ( import (
"net/http" "net/http"
"strings" "strings"
"time" "time"
"shared-sqs/app/interfaces" "shared-sqs/app/interfaces"
"shared-sqs/app/models" "shared-sqs/app/models"
"shared-sqs/app/utils" "shared-sqs/app/utils"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus" "github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
) )
func ChangeMessageVisibilityV1(req *http.Request) (int, interfaces.AbstractResponseBody) { func ChangeMessageVisibilityV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
requestBody := models.NewChangeMessageVisibilityRequest() requestBody := models.NewChangeMessageVisibilityRequest()
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
if !ok { if !ok {
log.Error("Invalid Request - ChangeMessageVisibilityV1") log.Error("Invalid Request - ChangeMessageVisibilityV1")
return utils.CreateErrorResponseV1("InvalidParameterValue", true) return utils.CreateErrorResponseV1("InvalidParameterValue", true)
} }
t := getTenantFromContext(req) t := getTenantFromContext(req)
if t == nil { if t == nil {
return utils.CreateErrorResponseV1("InvalidClientTokenId", true) return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
} }
vars := mux.Vars(req) vars := mux.Vars(req)
queueUrl := requestBody.QueueUrl queueUrl := requestBody.QueueUrl
queueName := "" queueName := ""
if queueUrl == "" { if queueUrl == "" {
queueName = vars["queueName"] queueName = vars["queueName"]
} else { } else {
uriSegments := strings.Split(queueUrl, "/") uriSegments := strings.Split(queueUrl, "/")
queueName = uriSegments[len(uriSegments)-1] queueName = uriSegments[len(uriSegments)-1]
} }
key := tenantQueueKey(t.AccessKey, queueName) key := tenantQueueKey(t.AccessKey, queueName)
receiptHandle := requestBody.ReceiptHandle receiptHandle := requestBody.ReceiptHandle
visibilityTimeout := requestBody.VisibilityTimeout visibilityTimeout := requestBody.VisibilityTimeout
if visibilityTimeout > 43200 { if visibilityTimeout < 0 || visibilityTimeout > 43200 {
return utils.CreateErrorResponseV1("ValidationError", true) return utils.CreateErrorResponseV1("InvalidParameterValue", true)
} }
if _, ok := models.SyncQueues.Queues[key]; !ok { if _, ok := models.SyncQueues.Queues[key]; !ok {
return utils.CreateErrorResponseV1("QueueNotFound", true) return utils.CreateErrorResponseV1("QueueNotFound", true)
} }
models.SyncQueues.Lock() models.SyncQueues.Lock()
messageFound := false messageFound := false
for i := 0; i < len(models.SyncQueues.Queues[key].Messages); i++ { for i := 0; i < len(models.SyncQueues.Queues[key].Messages); i++ {
queue := models.SyncQueues.Queues[key] queue := models.SyncQueues.Queues[key]
msgs := queue.Messages msgs := queue.Messages
if msgs[i].ReceiptHandle == receiptHandle { if msgs[i].ReceiptHandle == receiptHandle {
timeout := models.SyncQueues.Queues[key].VisibilityTimeout timeout := models.SyncQueues.Queues[key].VisibilityTimeout
if visibilityTimeout == 0 { if visibilityTimeout == 0 {
msgs[i].ReceiptTime = time.Now().UTC() msgs[i].ReceiptTime = time.Now().UTC()
msgs[i].ReceiptHandle = "" msgs[i].ReceiptHandle = ""
msgs[i].VisibilityTimeout = time.Now().Add(time.Duration(timeout) * time.Second) msgs[i].VisibilityTimeout = time.Now().Add(time.Duration(timeout) * time.Second)
msgs[i].Retry++ msgs[i].Retry++
if queue.MaxReceiveCount > 0 && if queue.MaxReceiveCount > 0 &&
queue.DeadLetterQueue != nil && queue.DeadLetterQueue != nil &&
msgs[i].Retry >= queue.MaxReceiveCount { msgs[i].Retry >= queue.MaxReceiveCount {
queue.DeadLetterQueue.Messages = append(queue.DeadLetterQueue.Messages, msgs[i]) queue.DeadLetterQueue.Messages = append(queue.DeadLetterQueue.Messages, msgs[i])
queue.Messages = append(queue.Messages[:i], queue.Messages[i+1:]...) queue.Messages = append(queue.Messages[:i], queue.Messages[i+1:]...)
} }
} else { } else {
msgs[i].VisibilityTimeout = time.Now().Add(time.Duration(visibilityTimeout) * time.Second) msgs[i].VisibilityTimeout = time.Now().Add(time.Duration(visibilityTimeout) * time.Second)
} }
messageFound = true messageFound = true
break break
} }
} }
models.SyncQueues.Unlock() models.SyncQueues.Unlock()
if !messageFound { if !messageFound {
return utils.CreateErrorResponseV1("MessageNotInFlight", true) return utils.CreateErrorResponseV1("MessageNotInFlight", true)
} }
respStruct := models.ChangeMessageVisibilityResult{ respStruct := models.ChangeMessageVisibilityResult{
Xmlns: models.BaseXmlns, Xmlns: models.BaseXmlns,
Metadata: models.BaseResponseMetadata, Metadata: models.BaseResponseMetadata,
} }
return http.StatusOK, &respStruct return http.StatusOK, &respStruct
} }
+9 -2
View File
@@ -57,14 +57,21 @@ func ReceiveMessageV1(req *http.Request) (int, interfaces.AbstractResponseBody)
var messages []*models.ResultMessage var messages []*models.ResultMessage
respStruct := models.ReceiveMessageResponse{} respStruct := models.ReceiveMessageResponse{}
// Валидация VisibilityTimeout: AWS SQS допускает 0–43200
if requestBody.VisibilityTimeout < 0 || requestBody.VisibilityTimeout > MaxVisibilityTimeout {
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
}
waitTimeSeconds := requestBody.WaitTimeSeconds waitTimeSeconds := requestBody.WaitTimeSeconds
if waitTimeSeconds == 0 { if waitTimeSeconds == 0 {
models.SyncQueues.RLock() models.SyncQueues.RLock()
waitTimeSeconds = models.SyncQueues.Queues[key].ReceiveMessageWaitTimeSeconds waitTimeSeconds = models.SyncQueues.Queues[key].ReceiveMessageWaitTimeSeconds
models.SyncQueues.RUnlock() models.SyncQueues.RUnlock()
} }
// Fix #4: clamp WaitTimeSeconds к AWS лимиту 0–20 // Валидация WaitTimeSeconds: AWS SQS допускает 0–20, иначе ошибка
waitTimeSeconds = ClampInt(waitTimeSeconds, 0, MaxReceiveMessageWaitTimeSeconds) if waitTimeSeconds < 0 || waitTimeSeconds > MaxReceiveMessageWaitTimeSeconds {
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
}
if waitTimeSeconds > 0 { if waitTimeSeconds > 0 {
deadline := time.Now().Add(time.Duration(waitTimeSeconds) * time.Second) deadline := time.Now().Add(time.Duration(waitTimeSeconds) * time.Second)
+3
View File
@@ -35,6 +35,9 @@ func SendMessageV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
} }
messageBody := requestBody.MessageBody messageBody := requestBody.MessageBody
if messageBody == "" {
return utils.CreateErrorResponseV1("MissingParameter", true)
}
messageGroupID := requestBody.MessageGroupId messageGroupID := requestBody.MessageGroupId
messageDeduplicationID := requestBody.MessageDeduplicationId messageDeduplicationID := requestBody.MessageDeduplicationId
+2
View File
@@ -22,6 +22,8 @@ func init() {
"ValidationError": {HttpError: http.StatusBadRequest, Type: "ValidationError", Code: "AWS.SimpleQueueService.ValidationError", Message: "The input fails to satisfy the constraints specified by an AWS service."}, "ValidationError": {HttpError: http.StatusBadRequest, Type: "ValidationError", Code: "AWS.SimpleQueueService.ValidationError", Message: "The input fails to satisfy the constraints specified by an AWS service."},
// LimitExceeded — превышен лимит очередей тенанта (max_queues) // LimitExceeded — превышен лимит очередей тенанта (max_queues)
"LimitExceeded": {HttpError: http.StatusBadRequest, Type: "LimitExceeded", Code: "AWS.SimpleQueueService.LimitExceeded", Message: "You've reached the limit on the number of queues."}, "LimitExceeded": {HttpError: http.StatusBadRequest, Type: "LimitExceeded", Code: "AWS.SimpleQueueService.LimitExceeded", Message: "You've reached the limit on the number of queues."},
// MissingParameter — обязательный параметр отсутствует (например, пустой MessageBody)
"MissingParameter": {HttpError: http.StatusBadRequest, Type: "MissingParameter", Code: "MissingParameter", Message: "The request must contain the parameter MessageBody."},
} }
SnsErrors = map[string]SnsErrorType{ SnsErrors = map[string]SnsErrorType{
"InvalidParameterValue": {HttpError: http.StatusBadRequest, Type: "InvalidParameterValue", Code: "AWS.SimpleNotificationService.InvalidParameterValue", Message: "An invalid or out-of-range value was supplied for the input parameter."}, "InvalidParameterValue": {HttpError: http.StatusBadRequest, Type: "InvalidParameterValue", Code: "AWS.SimpleNotificationService.InvalidParameterValue", Message: "An invalid or out-of-range value was supplied for the input parameter."},
+5 -4
View File
@@ -738,14 +738,15 @@ echo "── 15. Граничные случаи: имена, пустые те
# ═══════════════════════════════════════════ # ═══════════════════════════════════════════
# Очень длинное имя очереди (AWS позволяет до 80 символов) # Очень длинное имя очереди (AWS позволяет до 80 символов)
LONG_Q=$(printf 'q%.0s' {1..70})"-${TS}" # 69 q's + "-" + 10 цифр TS = 80 символов (ровно лимит)
LONG_Q=$(printf 'q%.0s' {1..69})"-${TS}"
LONG_QURL=$(qurl "$T1_ID" "$LONG_Q") LONG_QURL=$(qurl "$T1_ID" "$LONG_Q")
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$LONG_Q" 2>&1) R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$LONG_Q" 2>&1)
check "Граница: длинное имя очереди (70 сим)" "$R" "Queue" check "Граница: длинное имя очереди (80 сим)" "$R" "Queue"
# Пустое тело сообщения # Пустое тело сообщения — AWS SQS требует непустой MessageBody
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$LONG_QURL" --message-body "" 2>&1) R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$LONG_QURL" --message-body "" 2>&1)
check "Граница: пустое тело сообщения" "$R" "MessageId" check "Граница: пустое тело сообщения → ошибка" "$R" "ERROR|Error|error|MissingParameter|Invalid"
# Receive из пустой очереди # Receive из пустой очереди
sqs "$T1_AK" "$T1_SK" purge-queue --queue-url "$LONG_QURL" >/dev/null 2>&1 || true sqs "$T1_AK" "$T1_SK" purge-queue --queue-url "$LONG_QURL" >/dev/null 2>&1 || true