Repository: getmoto/moto

Issue:
Error code confusion in mocked responses?
It appears an error response including `Code` and `Message` is rendered using `moto.core.exceptions.ERROR_RESPONSE`, which is passed to the template via `RESTError(error_type, message)` where `error_type` gets rendered into `Code` of the template.

In `exceptions.py` of service submodules, `error_type` often is a short descriptive text, instead of HTTP status code which appears to be the case in many non-mocked responses. (In fact, real responses are indeed inconsistent in `Code` being a numeric status code vs. short text...)

Unfortunate side effect of the difference is that an idiomatic code like the following does not work properly (this is from a `boto3` example page):

```
import botocore
bucket = s3.Bucket('mybucket')
exists = True
try:
    s3.meta.client.head_bucket(Bucket='mybucket')
except botocore.exceptions.ClientError as e:
    # If a client error is thrown, then check that it was a 404 error.
    # If it was a 404 error, then the bucket does not exist.
    error_code = int(e.response['Error']['Code'])
    if error_code == 404:
        exists = False
```

since `e.response['Error']['Code']` of mocked response, being a text, cannot be cast properly to `int`.

I'm wondering if this is a design decision of some sort of oversight? Is there a reason why `Code` and `Message` are not mocked closely to the actual responses?


Discussion:
@okomestudio can you share what the `e.response['Error']['Code']` is from that example? Is it 'NoSuchBucket'? If so, I'm confused why the boto3 docs suggest that would work, I think it wouldn't.
The specific example snippet is from this page: http://boto3.readthedocs.io/en/latest/guide/migrations3.html

I think the return value of `response['Error']['Code']` might be inconsistently implemented (numeric code vs. `NoSuchKey`) in the AWS API itself, and I initially found this out when I recently updated `moto` to the recent version and a couple existing tests (on EMR resources) started failing due to this.

This might be one of those things we have to discover by actually hitting the API and inspecting responses manually.
We recently changed this very response code in Moto. When you issue a GET
request you get NoSuchKey and when you issue a HEAD request you get a 404.
Or perhaps that's backwards. Regardless, it was a bug where a user found
the AWS API used different responses for those two HTTP verbs so we matched
the live AWS response.
Il giorno lun 17 lug 2017 alle 5:35 PM Taro Sato <notifications@github.com>
ha scritto:

> The specific example snippet is from this page:
> http://boto3.readthedocs.io/en/latest/guide/migrations3.html
>
> I think the return value of response['Error']['Code'] might be
> inconsistently implemented (numeric code vs. NoSuchKey) in the AWS API
> itself, and I initially found this out when I recently updated moto to
> the recent version and a couple existing tests (on EMR resources) started
> failing due to this.
>
> This might be one of those things we have to discover by actually hitting
> the API and inspecting responses manually.
>
> —
> You are receiving this because you commented.
>
>
> Reply to this email directly, view it on GitHub
> <https://github.com/spulec/moto/issues/1004#issuecomment-315924049>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AAAIFyupDSRY4sKzqZN40RCsET9qQQC7ks5sO_3SgaJpZM4OThQx>
> .
>

Okay, I now understand that the intention is exactly match what the real AWS API responses return, which is great. 

Probably I should get a patch in as PR where I find mismatch in an error response.
That would be great, thank you

Resolving patch:
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -13,7 +13,7 @@
 from moto.s3bucket_path.utils import bucket_name_from_url as bucketpath_bucket_name_from_url, parse_key_name as bucketpath_parse_key_name, is_delete_keys as bucketpath_is_delete_keys
 
 
-from .exceptions import BucketAlreadyExists, S3ClientError, MissingKey, InvalidPartOrder
+from .exceptions import BucketAlreadyExists, S3ClientError, MissingBucket, MissingKey, InvalidPartOrder
 from .models import s3_backend, get_canned_acl, FakeGrantee, FakeGrant, FakeAcl, FakeKey, FakeTagging, FakeTagSet, FakeTag
 from .utils import bucket_name_from_url, metadata_from_headers
 from xml.dom import minidom
@@ -155,7 +155,14 @@ def _bucket_response(self, request, full_url, headers):
                 "Method {0} has not been impelemented in the S3 backend yet".format(method))
 
     def _bucket_response_head(self, bucket_name, headers):
-        self.backend.get_bucket(bucket_name)
+        try:
+            self.backend.get_bucket(bucket_name)
+        except MissingBucket:
+            # Unless we do this, boto3 does not raise ClientError on
+            # HEAD (which the real API responds with), and instead
+            # raises NoSuchBucket, leading to inconsistency in
+            # error response between real and mocked responses.
+            return 404, {}, "Not Found"
         return 200, {}, ""
 
     def _bucket_response_get(self, bucket_name, querystring, headers):


Test patch:
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -1250,6 +1250,31 @@ def test_boto3_head_object():
     e.exception.response['Error']['Code'].should.equal('404')
 
 
+@mock_s3
+def test_boto3_bucket_deletion():
+    cli = boto3.client('s3', region_name='us-east-1')
+    cli.create_bucket(Bucket="foobar")
+
+    cli.put_object(Bucket="foobar", Key="the-key", Body="some value")
+
+    # Try to delete a bucket that still has keys
+    cli.delete_bucket.when.called_with(Bucket="foobar").should.throw(
+        cli.exceptions.ClientError,
+        ('An error occurred (BucketNotEmpty) when calling the DeleteBucket operation: '
+         'The bucket you tried to delete is not empty'))
+
+    cli.delete_object(Bucket="foobar", Key="the-key")
+    cli.delete_bucket(Bucket="foobar")
+
+    # Get non-existing bucket
+    cli.head_bucket.when.called_with(Bucket="foobar").should.throw(
+        cli.exceptions.ClientError,
+        "An error occurred (404) when calling the HeadBucket operation: Not Found")
+
+    # Delete non-existing bucket
+    cli.delete_bucket.when.called_with(Bucket="foobar").should.throw(cli.exceptions.NoSuchBucket)
+
+
 @mock_s3
 def test_boto3_get_object():
     s3 = boto3.resource('s3', region_name='us-east-1')
@@ -1560,4 +1585,3 @@ def test_get_stream_gzipped():
     </ns0:RoutingRules>
 </ns0:WebsiteConfiguration>
 """
-