Repository: getmoto/moto

Issue:
Mock SQS: incorrect handling of bad queue URLs
On real SQS, when I try to access a queue with invalid URL (e.g. `sqs.Queue('http://some-unknown-name').load()`), I get `QueueDoesNotExist` error.
When using Moto mock, the error I get is different: `UndefinedError: 'None' has no attribute 'attributes'`.
Will attach full trace below but it is not very helpful because BotoCore hides original stack trace.
I tracked this down and found that the exception happens in `moto/sqs/responses.py`, in `SQSResponse.get_queue_attributes`, when trying to render template `GET_QUEUE_ATTRIBUTES_RESPONSE`. This happens because `self.sqs_backend.get_queue(queue_name)` returns None.
The code is in `moto/sqs/models.py`, in `SQSBackend.get_queue`. It just uses `self.queues.get(name, None)` thus falling back to None if there is no such queue. I think that it should raise an exception instead.
Also it should probably make some better validation in `SQSResponse._get_queue_url` than just fetch last slash-delimited part. Or validate URL somewherre else.. 

How to reproduce:
```
import moto
import boto3

with moto.mock_sqs():
    sqs = boto3.resource('sqs')
	sqs.Queue('http://whatever-incorrect-queue-address').load()
```
Expected result (the one that happens without using `mock_sqs`):
```
QueueDoesNotExist: An error occurred (AWS.SimpleQueueService.NonExistentQueue) when calling the GetQueueAttributes operation: The specified queue does not exist for this wsdl version.
```
Actual result:
```
Traceback (most recent call last):
  File "/tmp/test.py", line 9, in <module>
    sqs.Queue('http://abc/whatever').load()
  File "/tmp/env/lib/python2.7/site-packages/boto3/resources/factory.py", line 505, in do_action
    response = action(self, *args, **kwargs)
  File "/tmp/env/lib/python2.7/site-packages/boto3/resources/action.py", line 83, in __call__
    response = getattr(parent.meta.client, operation_name)(**params)
  File "/tmp/env/lib/python2.7/site-packages/botocore/client.py", line 310, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/tmp/env/lib/python2.7/site-packages/botocore/client.py", line 586, in _make_api_call
    operation_model, request_dict)
  File "/tmp/env/lib/python2.7/site-packages/botocore/endpoint.py", line 141, in make_request
    return self._send_request(request_dict, operation_model)
  File "/tmp/env/lib/python2.7/site-packages/botocore/endpoint.py", line 170, in _send_request
    success_response, exception):
  File "/tmp/env/lib/python2.7/site-packages/botocore/endpoint.py", line 249, in _needs_retry
    caught_exception=caught_exception, request_dict=request_dict)
  File "/tmp/env/lib/python2.7/site-packages/botocore/hooks.py", line 227, in emit
    return self._emit(event_name, kwargs)
  File "/tmp/env/lib/python2.7/site-packages/botocore/hooks.py", line 210, in _emit
    response = handler(**kwargs)
  File "/tmp/env/lib/python2.7/site-packages/botocore/retryhandler.py", line 183, in __call__
    if self._checker(attempts, response, caught_exception):
  File "/tmp/env/lib/python2.7/site-packages/botocore/retryhandler.py", line 251, in __call__
    caught_exception)
  File "/tmp/env/lib/python2.7/site-packages/botocore/retryhandler.py", line 269, in _should_retry
    return self._checker(attempt_number, response, caught_exception)
  File "/tmp/env/lib/python2.7/site-packages/botocore/retryhandler.py", line 317, in __call__
    caught_exception)
  File "/tmp/env/lib/python2.7/site-packages/botocore/retryhandler.py", line 223, in __call__
    attempt_number, caught_exception)
  File "/tmp/env/lib/python2.7/site-packages/botocore/retryhandler.py", line 359, in _check_caught_exception
    raise caught_exception
jinja2.exceptions.UndefinedError: 'None' has no attribute 'attributes'
```

Moto version: `moto-ext==1.0.1.7`, installed with `pip`.



Discussion:

Resolving patch:
diff --git a/moto/sqs/exceptions.py b/moto/sqs/exceptions.py
--- a/moto/sqs/exceptions.py
+++ b/moto/sqs/exceptions.py
@@ -16,3 +16,8 @@ class MessageAttributesInvalid(Exception):
 
     def __init__(self, description):
         self.description = description
+
+
+class QueueDoesNotExist(Exception):
+    status_code = 404
+    description = "The specified queue does not exist for this wsdl version."
diff --git a/moto/sqs/models.py b/moto/sqs/models.py
--- a/moto/sqs/models.py
+++ b/moto/sqs/models.py
@@ -12,7 +12,12 @@
 from moto.core import BaseBackend, BaseModel
 from moto.core.utils import camelcase_to_underscores, get_random_message_id, unix_time, unix_time_millis
 from .utils import generate_receipt_handle
-from .exceptions import ReceiptHandleIsInvalid, MessageNotInflight, MessageAttributesInvalid
+from .exceptions import (
+    MessageAttributesInvalid,
+    MessageNotInflight,
+    QueueDoesNotExist,
+    ReceiptHandleIsInvalid,
+)
 
 DEFAULT_ACCOUNT_ID = 123456789012
 DEFAULT_SENDER_ID = "AIDAIT2UOQQY3AUEKVGXU"
@@ -304,7 +309,10 @@ def list_queues(self, queue_name_prefix):
         return qs
 
     def get_queue(self, queue_name):
-        return self.queues.get(queue_name, None)
+        queue = self.queues.get(queue_name)
+        if queue is None:
+            raise QueueDoesNotExist()
+        return queue
 
     def delete_queue(self, queue_name):
         if queue_name in self.queues:
diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py
--- a/moto/sqs/responses.py
+++ b/moto/sqs/responses.py
@@ -8,7 +8,8 @@
 from .exceptions import (
     MessageAttributesInvalid,
     MessageNotInflight,
-    ReceiptHandleIsInvalid
+    QueueDoesNotExist,
+    ReceiptHandleIsInvalid,
 )
 
 MAXIMUM_VISIBILTY_TIMEOUT = 43200
@@ -76,7 +77,12 @@ def create_queue(self):
     def get_queue_url(self):
         request_url = urlparse(self.uri)
         queue_name = self._get_param("QueueName")
-        queue = self.sqs_backend.get_queue(queue_name)
+
+        try:
+            queue = self.sqs_backend.get_queue(queue_name)
+        except QueueDoesNotExist as e:
+            return self._error('QueueDoesNotExist', e.description)
+
         if queue:
             template = self.response_template(GET_QUEUE_URL_RESPONSE)
             return template.render(queue=queue, request_url=request_url)
@@ -113,7 +119,11 @@ def change_message_visibility(self):
 
     def get_queue_attributes(self):
         queue_name = self._get_queue_name()
-        queue = self.sqs_backend.get_queue(queue_name)
+        try:
+            queue = self.sqs_backend.get_queue(queue_name)
+        except QueueDoesNotExist as e:
+            return self._error('QueueDoesNotExist', e.description)
+
         template = self.response_template(GET_QUEUE_ATTRIBUTES_RESPONSE)
         return template.render(queue=queue)
 
@@ -250,7 +260,11 @@ def purge_queue(self):
 
     def receive_message(self):
         queue_name = self._get_queue_name()
-        queue = self.sqs_backend.get_queue(queue_name)
+
+        try:
+            queue = self.sqs_backend.get_queue(queue_name)
+        except QueueDoesNotExist as e:
+            return self._error('QueueDoesNotExist', e.description)
 
         try:
             message_count = int(self.querystring.get("MaxNumberOfMessages")[0])


Test patch:
diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py
--- a/tests/test_sqs/test_sqs.py
+++ b/tests/test_sqs/test_sqs.py
@@ -4,6 +4,7 @@
 import boto
 import boto3
 import botocore.exceptions
+from botocore.exceptions import ClientError
 from boto.exception import SQSError
 from boto.sqs.message import RawMessage, Message
 
@@ -33,6 +34,7 @@ def test_create_fifo_queue_fail():
     else:
         raise RuntimeError('Should of raised InvalidParameterValue Exception')
 
+
 @mock_sqs
 def test_create_fifo_queue():
     sqs = boto3.client('sqs', region_name='us-east-1')
@@ -49,10 +51,10 @@ def test_create_fifo_queue():
     response['Attributes']['FifoQueue'].should.equal('true')
 
 
-
 @mock_sqs
 def test_create_queue():
     sqs = boto3.resource('sqs', region_name='us-east-1')
+
     new_queue = sqs.create_queue(QueueName='test-queue')
     new_queue.should_not.be.none
     new_queue.should.have.property('url').should.contain('test-queue')
@@ -66,10 +68,19 @@ def test_create_queue():
 
 
 @mock_sqs
-def test_get_inexistent_queue():
+def test_get_nonexistent_queue():
     sqs = boto3.resource('sqs', region_name='us-east-1')
-    sqs.get_queue_by_name.when.called_with(
-        QueueName='nonexisting-queue').should.throw(botocore.exceptions.ClientError)
+    with assert_raises(ClientError) as err:
+        sqs.get_queue_by_name(QueueName='nonexisting-queue')
+    ex = err.exception
+    ex.operation_name.should.equal('GetQueueUrl')
+    ex.response['Error']['Code'].should.equal('QueueDoesNotExist')
+
+    with assert_raises(ClientError) as err:
+        sqs.Queue('http://whatever-incorrect-queue-address').load()
+    ex = err.exception
+    ex.operation_name.should.equal('GetQueueAttributes')
+    ex.response['Error']['Code'].should.equal('QueueDoesNotExist')
 
 
 @mock_sqs