Repository: localstack/localstack

Issue:
bug: Cannot create an SQS queue with FifoQueue: false
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

I can't create an SQS Queue using the AWS JavaScript SDK with FifoQueue: "false". Attempting to do so gives an error of `InvalidAttributeName: Unknown Attribute FifoQueue` and metadata saying that localstack responded with a 400.

### Expected Behavior

The queue is created successfully.

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

docker-compose.yml

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

```typescript
const sqs = new SQSClient({ endpoint: "http://localstack:4566", credentials: { accessKeyId: "foo", secretAccessKey: "bar", }, region: "us-east-1" });

await sqs.send(
    new CreateQueueCommand({
      QueueName: "test-queue",
      Attributes: {
        FifoQueue: "false",
      },
    }),
  );
```


### Environment

```markdown
- OS: Ubuntu 22.04
- LocalStack: 3.4.0
- @aws-sdk/client-sqs version: 3.370.0
```


### Anything else?

_No response_


Discussion:
Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/contact/) for real-time help and drop a message to LocalStack Pro Support if you are a Pro user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md) and our [contributing guide](https://docs.localstack.cloud/contributing/).

Resolving patch:
diff --git a/localstack/services/sqs/constants.py b/localstack/services/sqs/constants.py
--- a/localstack/services/sqs/constants.py
+++ b/localstack/services/sqs/constants.py
@@ -31,6 +31,12 @@
     QueueAttributeName.QueueArn,
 ]
 
+INVALID_STANDARD_QUEUE_ATTRIBUTES = [
+    QueueAttributeName.FifoQueue,
+    QueueAttributeName.ContentBasedDeduplication,
+    *INTERNAL_QUEUE_ATTRIBUTES,
+]
+
 # URL regexes for various endpoint strategies
 STANDARD_STRATEGY_URL_REGEX = r"sqs.(?P<region_name>[a-z0-9-]{1,})\.[^:]+:\d{4,5}\/(?P<account_id>\d{12})\/(?P<queue_name>[a-zA-Z0-9_-]+(.fifo)?)$"
 DOMAIN_STRATEGY_URL_REGEX = r"((?P<region_name>[a-z0-9-]{1,})\.)?queue\.[^:]+:\d{4,5}\/(?P<account_id>\d{12})\/(?P<queue_name>[a-zA-Z0-9_-]+(.fifo)?)$"
diff --git a/localstack/services/sqs/models.py b/localstack/services/sqs/models.py
--- a/localstack/services/sqs/models.py
+++ b/localstack/services/sqs/models.py
@@ -290,6 +290,7 @@ def __init__(self, name: str, region: str, account_id: str, attributes=None, tag
 
         self.attributes = self.default_attributes()
         if attributes:
+            self.validate_queue_attributes(attributes)
             self.attributes.update(attributes)
 
         self.purge_in_progress = False
@@ -588,16 +589,7 @@ def _assert_queue_name(self, name):
             )
 
     def validate_queue_attributes(self, attributes):
-        valid = [
-            k[1]
-            for k in inspect.getmembers(QueueAttributeName)
-            if k not in sqs_constants.INTERNAL_QUEUE_ATTRIBUTES
-        ]
-        del valid[valid.index(QueueAttributeName.FifoQueue)]
-
-        for k in attributes.keys():
-            if k not in valid:
-                raise InvalidAttributeName(f"Unknown Attribute {k}.")
+        pass
 
     def add_permission(self, label: str, actions: list[str], account_ids: list[str]) -> None:
         """
@@ -881,6 +873,23 @@ def _on_remove_message(self, message: SqsMessage):
                 # this may happen if the message no longer exists because it was removed earlier
                 pass
 
+    def validate_queue_attributes(self, attributes):
+        valid = [
+            k[1]
+            for k in inspect.getmembers(
+                QueueAttributeName, lambda x: isinstance(x, str) and not x.startswith("__")
+            )
+            if k[1] not in sqs_constants.INVALID_STANDARD_QUEUE_ATTRIBUTES
+        ]
+
+        for k in attributes.keys():
+            if k in [QueueAttributeName.FifoThroughputLimit, QueueAttributeName.DeduplicationScope]:
+                raise InvalidAttributeName(
+                    f"You can specify the {k} only when FifoQueue is set to true."
+                )
+            if k not in valid:
+                raise InvalidAttributeName(f"Unknown Attribute {k}.")
+
 
 class MessageGroup:
     message_group_id: str


Test patch:
diff --git a/tests/aws/services/cloudformation/resources/test_sqs.py b/tests/aws/services/cloudformation/resources/test_sqs.py
--- a/tests/aws/services/cloudformation/resources/test_sqs.py
+++ b/tests/aws/services/cloudformation/resources/test_sqs.py
@@ -28,19 +28,19 @@ def test_sqs_fifo_queue_generates_valid_name(deploy_cfn_template):
         template_path=os.path.join(
             os.path.dirname(__file__), "../../../templates/sqs_fifo_autogenerate_name.yaml"
         ),
-        template_mapping={"is_fifo": "true"},
+        parameters={"IsFifo": "true"},
+        max_wait=240,
     )
     assert ".fifo" in result.outputs["FooQueueName"]
 
 
-# FIXME: doesn't work on AWS. (known bug in cloudformation: https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/165)
-@markers.aws.unknown
+@markers.aws.validated
 def test_sqs_non_fifo_queue_generates_valid_name(deploy_cfn_template):
     result = deploy_cfn_template(
         template_path=os.path.join(
             os.path.dirname(__file__), "../../../templates/sqs_fifo_autogenerate_name.yaml"
         ),
-        template_mapping={"is_fifo": "false"},
+        parameters={"IsFifo": "false"},
         max_wait=240,
     )
     assert ".fifo" not in result.outputs["FooQueueName"]
diff --git a/tests/aws/services/cloudformation/resources/test_sqs.validation.json b/tests/aws/services/cloudformation/resources/test_sqs.validation.json
--- a/tests/aws/services/cloudformation/resources/test_sqs.validation.json
+++ b/tests/aws/services/cloudformation/resources/test_sqs.validation.json
@@ -1,4 +1,10 @@
 {
+  "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_fifo_queue_generates_valid_name": {
+    "last_validated_date": "2024-05-15T02:01:00+00:00"
+  },
+  "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_non_fifo_queue_generates_valid_name": {
+    "last_validated_date": "2024-05-15T01:59:34+00:00"
+  },
   "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_queue_no_change": {
     "last_validated_date": "2023-12-08T20:11:26+00:00"
   },
diff --git a/tests/aws/services/sqs/test_sqs.py b/tests/aws/services/sqs/test_sqs.py
--- a/tests/aws/services/sqs/test_sqs.py
+++ b/tests/aws/services/sqs/test_sqs.py
@@ -627,6 +627,29 @@ def test_create_fifo_queue_with_different_attributes_raises_error(
             )
         snapshot.match("queue-already-exists", e.value.response)
 
+    @markers.aws.validated
+    def test_create_standard_queue_with_fifo_attribute_raises_error(
+        self, sqs_create_queue, aws_sqs_client, snapshot
+    ):
+        queue_name = f"queue-{short_uid()}"
+        with pytest.raises(ClientError) as e:
+            sqs_create_queue(QueueName=queue_name, Attributes={"FifoQueue": "false"})
+        snapshot.match("invalid-attribute-fifo-queue", e.value.response)
+
+        with pytest.raises(ClientError) as e:
+            sqs_create_queue(
+                QueueName=queue_name, Attributes={"ContentBasedDeduplication": "false"}
+            )
+        snapshot.match("invalid-attribute-content-based-deduplication", e.value.response)
+
+        with pytest.raises(ClientError) as e:
+            sqs_create_queue(QueueName=queue_name, Attributes={"DeduplicationScope": "queue"})
+        snapshot.match("invalid-attribute-deduplication-scope", e.value.response)
+
+        with pytest.raises(ClientError) as e:
+            sqs_create_queue(QueueName=queue_name, Attributes={"FifoThroughputLimit": "perQueue"})
+        snapshot.match("invalid-attribute-throughput-limit", e.value.response)
+
     @markers.aws.validated
     def test_send_message_with_delay_0_works_for_fifo(self, sqs_create_queue, aws_sqs_client):
         # see issue https://github.com/localstack/localstack/issues/6612
@@ -3034,10 +3057,25 @@ def test_get_specific_queue_attribute_response(
         assert constructed_arn == get_single_attribute.get("Attributes").get("QueueArn")
         assert max_receive_count == redrive_policy.get("maxReceiveCount")
 
-    @pytest.mark.xfail
+    @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Detail", "$..Error.Type"])
+    @markers.aws.validated
+    def test_set_unsupported_attribute_standard(self, sqs_create_queue, aws_sqs_client, snapshot):
+        queue_name = f"queue-{short_uid()}"
+        queue_url = sqs_create_queue(QueueName=queue_name)
+        with pytest.raises(ClientError) as e:
+            aws_sqs_client.set_queue_attributes(
+                QueueUrl=queue_url, Attributes={"FifoQueue": "true"}
+            )
+        snapshot.match("invalid-attr-name-1", e.value.response)
+        with pytest.raises(ClientError) as e:
+            aws_sqs_client.set_queue_attributes(
+                QueueUrl=queue_url, Attributes={"FifoQueue": "false"}
+            )
+        snapshot.match("invalid-attr-name-2", e.value.response)
+
+    @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Detail", "$..Error.Type"])
     @markers.aws.validated
     def test_set_unsupported_attribute_fifo(self, sqs_create_queue, aws_sqs_client, snapshot):
-        # TODO: behaviour diverges from AWS
         queue_name = f"queue-{short_uid()}"
         queue_url = sqs_create_queue(QueueName=queue_name)
         with pytest.raises(ClientError) as e:
diff --git a/tests/aws/services/sqs/test_sqs.snapshot.json b/tests/aws/services/sqs/test_sqs.snapshot.json
--- a/tests/aws/services/sqs/test_sqs.snapshot.json
+++ b/tests/aws/services/sqs/test_sqs.snapshot.json
@@ -1320,12 +1320,64 @@
     }
   },
   "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs]": {
-    "recorded-date": "30-04-2024, 13:33:56",
-    "recorded-content": {}
+    "recorded-date": "14-05-2024, 22:23:46",
+    "recorded-content": {
+      "invalid-attr-name-1": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "Unknown Attribute FifoQueue.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "Unknown Attribute FifoQueue.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attr-name-2": {
+        "Error": {
+          "Code": "InvalidAttributeValue",
+          "Message": "Invalid value for the parameter FifoQueue. Reason: Modifying queue type is not supported.",
+          "QueryErrorCode": "InvalidAttributeValue",
+          "Type": "Sender"
+        },
+        "message": "Invalid value for the parameter FifoQueue. Reason: Modifying queue type is not supported.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      }
+    }
   },
   "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs_query]": {
-    "recorded-date": "30-04-2024, 13:33:57",
-    "recorded-content": {}
+    "recorded-date": "14-05-2024, 22:23:47",
+    "recorded-content": {
+      "invalid-attr-name-1": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Detail": null,
+          "Message": "Unknown Attribute FifoQueue.",
+          "Type": "Sender"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attr-name-2": {
+        "Error": {
+          "Code": "InvalidAttributeValue",
+          "Detail": null,
+          "Message": "Invalid value for the parameter FifoQueue. Reason: Modifying queue type is not supported.",
+          "Type": "Sender"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      }
+    }
   },
   "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-True]": {
     "recorded-date": "30-04-2024, 13:34:01",
@@ -3022,5 +3074,179 @@
         }
       }
     }
+  },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs]": {
+    "recorded-date": "14-05-2024, 22:34:35",
+    "recorded-content": {
+      "invalid-attr-name-1": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "Unknown Attribute FifoQueue.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "Unknown Attribute FifoQueue.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attr-name-2": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "Unknown Attribute FifoQueue.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "Unknown Attribute FifoQueue.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      }
+    }
+  },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs_query]": {
+    "recorded-date": "14-05-2024, 22:34:35",
+    "recorded-content": {
+      "invalid-attr-name-1": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Detail": null,
+          "Message": "Unknown Attribute FifoQueue.",
+          "Type": "Sender"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attr-name-2": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Detail": null,
+          "Message": "Unknown Attribute FifoQueue.",
+          "Type": "Sender"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      }
+    }
+  },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs]": {
+    "recorded-date": "15-05-2024, 02:30:47",
+    "recorded-content": {
+      "invalid-attribute-fifo-queue": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "Unknown Attribute FifoQueue.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "Unknown Attribute FifoQueue.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attribute-content-based-deduplication": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "Unknown Attribute ContentBasedDeduplication.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "Unknown Attribute ContentBasedDeduplication.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attribute-deduplication-scope": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "You can specify the DeduplicationScope only when FifoQueue is set to true.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "You can specify the DeduplicationScope only when FifoQueue is set to true.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attribute-throughput-limit": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "You can specify the FifoThroughputLimit only when FifoQueue is set to true.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "You can specify the FifoThroughputLimit only when FifoQueue is set to true.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      }
+    }
+  },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs_query]": {
+    "recorded-date": "15-05-2024, 02:30:48",
+    "recorded-content": {
+      "invalid-attribute-fifo-queue": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "Unknown Attribute FifoQueue.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "Unknown Attribute FifoQueue.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attribute-content-based-deduplication": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "Unknown Attribute ContentBasedDeduplication.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "Unknown Attribute ContentBasedDeduplication.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attribute-deduplication-scope": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "You can specify the DeduplicationScope only when FifoQueue is set to true.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "You can specify the DeduplicationScope only when FifoQueue is set to true.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      },
+      "invalid-attribute-throughput-limit": {
+        "Error": {
+          "Code": "InvalidAttributeName",
+          "Message": "You can specify the FifoThroughputLimit only when FifoQueue is set to true.",
+          "QueryErrorCode": "InvalidAttributeName",
+          "Type": "Sender"
+        },
+        "message": "You can specify the FifoThroughputLimit only when FifoQueue is set to true.",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      }
+    }
   }
 }
diff --git a/tests/aws/services/sqs/test_sqs.validation.json b/tests/aws/services/sqs/test_sqs.validation.json
--- a/tests/aws/services/sqs/test_sqs.validation.json
+++ b/tests/aws/services/sqs/test_sqs.validation.json
@@ -41,6 +41,12 @@
   "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs_query]": {
     "last_validated_date": "2024-04-30T13:33:20+00:00"
   },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs]": {
+    "last_validated_date": "2024-05-15T02:30:47+00:00"
+  },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs_query]": {
+    "last_validated_date": "2024-05-15T02:30:48+00:00"
+  },
   "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_message_attributes": {
     "last_validated_date": "2024-04-30T13:33:55+00:00"
   },
@@ -272,6 +278,18 @@
   "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_multiple_queues": {
     "last_validated_date": "2024-04-30T13:40:05+00:00"
   },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs]": {
+    "last_validated_date": "2024-05-14T22:23:46+00:00"
+  },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs_query]": {
+    "last_validated_date": "2024-05-14T22:23:47+00:00"
+  },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs]": {
+    "last_validated_date": "2024-05-14T22:34:35+00:00"
+  },
+  "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs_query]": {
+    "last_validated_date": "2024-05-14T22:34:35+00:00"
+  },
   "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_same_dedup_id_different_message_groups[sqs]": {
     "last_validated_date": "2024-04-30T13:51:17+00:00"
   },
diff --git a/tests/aws/templates/sqs_fifo_autogenerate_name.yaml b/tests/aws/templates/sqs_fifo_autogenerate_name.yaml
--- a/tests/aws/templates/sqs_fifo_autogenerate_name.yaml
+++ b/tests/aws/templates/sqs_fifo_autogenerate_name.yaml
@@ -1,9 +1,16 @@
+Parameters:
+  IsFifo:
+    Type: String
+
+Conditions:
+  IsFifo: !Equals [ !Ref IsFifo, "true"]
+
 Resources:
   FooQueueA2A23E59:
     Type: AWS::SQS::Queue
     Properties:
-      ContentBasedDeduplication: true
-      FifoQueue: {{ is_fifo }}
+      ContentBasedDeduplication: !If [ IsFifo, "true", !Ref AWS::NoValue ]
+      FifoQueue: !If [ IsFifo, "true", !Ref AWS::NoValue ]
       VisibilityTimeout: 300
     UpdateReplacePolicy: Delete
     DeletionPolicy: Delete