Repository: adobe/s3mock
Issue: Copy command returns generic 404 instead of a NoSuchKey error

**Exception**

When trying to copy a non-existing S3 object to the same bucket but with a different key a generic 404 exception occurs.

```
Exception in thread "main" software.amazon.awssdk.services.s3.model.S3Exception: null (Service: S3, Status Code: 404, Request ID: null, Extended Request ID: null)
	at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handleErrorResponse(AwsXmlPredicatedResponseHandler.java:156)
	at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handleResponse(AwsXmlPredicatedResponseHandler.java:106)
	at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handle(AwsXmlPredicatedResponseHandler.java:84)
	at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handle(AwsXmlPredicatedResponseHandler.java:42)
```

**Reproducing  the issue**

I use the following scratch file :

```java
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.CopyObjectRequest;

import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

class S3Test {

    public static void main(String[] args) throws IOException {
        final S3Client s3Client = S3Client.builder()
                                          .endpointOverride(URI.create("http://localhost:9090"))
                                          .build();

        final String copySource = URLEncoder.encode("my-bucket/test-file.json", StandardCharsets.UTF_8.toString());

        s3Client.copyObject(CopyObjectRequest.builder()
                                             .copySource(copySource)
                                             .destinationBucket("my-bucket")
                                             .destinationKey("test-file-copy.json")
                                             .build());
    }
}
```

Please note I set this environment variable to initialize the bucket : *initialBuckets=my-bucket*

**Possible fix**

A quick fix I found was to change `FileStore`'s line 701 :

```java
  // ...
  public CopyObjectResult copyS3ObjectEncrypted(final String sourceBucketName,
      final String sourceObjectName,
      final String destinationBucketName,
      final String destinationObjectName,
      final String encryption,
      final String kmsKeyId,
      final Map<String, String> userMetadata) throws IOException {
    final S3Object sourceObject = getS3Object(sourceBucketName, sourceObjectName);
    if (sourceObject == null) {
      //return null;
      throw new S3Exception(NOT_FOUND.value(), "NoSuchKey", "The specified key does not exist.");
    }
    // ...
```

I can raise a Pull Request if needed 😊 


Changed files:
integration-tests/src/test/java/com/adobe/testing/s3mock/its/AmazonClientUploadIT.java
integration-tests/src/test/java/com/adobe/testing/s3mock/its/MultiPartUploadIT.java
server/src/main/java/com/adobe/testing/s3mock/FileStoreController.java

diff --git a/integration-tests/src/test/java/com/adobe/testing/s3mock/its/AmazonClientUploadIT.java b/integration-tests/src/test/java/com/adobe/testing/s3mock/its/AmazonClientUploadIT.java
index 5e94ca91..69960bda 100644
--- a/integration-tests/src/test/java/com/adobe/testing/s3mock/its/AmazonClientUploadIT.java
+++ b/integration-tests/src/test/java/com/adobe/testing/s3mock/its/AmazonClientUploadIT.java
@@ -675,6 +675,26 @@ class AmazonClientUploadIT extends S3TestBase {
         .hasMessageContaining("Status Code: 400; Error Code: KMS.NotFoundException");
   }
 
+  /**
+   * Tests that a copy request for a non-existing object throws the correct error.
+   */
+  @Test
+  void shouldThrowNoSuchKeyOnCopyForNonExistingKey() {
+    final String sourceKey = "NON_EXISTENT_KEY";
+    final String destinationBucketName = "destinationbucket";
+    final String destinationKey = "copyOf" + sourceKey;
+
+    s3Client.createBucket(BUCKET_NAME);
+    s3Client.createBucket(destinationBucketName);
+
+    final CopyObjectRequest copyObjectRequest =
+        new CopyObjectRequest(BUCKET_NAME, sourceKey, destinationBucketName, destinationKey);
+
+    assertThatThrownBy(() -> s3Client.copyObject(copyObjectRequest))
+        .isInstanceOf(AmazonS3Exception.class)
+        .hasMessageContaining("Status Code: 404; Error Code: NoSuchKey");
+  }
+
   /**
    * Creates a bucket and checks if it exists using {@link AmazonS3Client#doesBucketExist(String)}.
    */
diff --git a/integration-tests/src/test/java/com/adobe/testing/s3mock/its/MultiPartUploadIT.java b/integration-tests/src/test/java/com/adobe/testing/s3mock/its/MultiPartUploadIT.java
index d648acab..ab183703 100644
--- a/integration-tests/src/test/java/com/adobe/testing/s3mock/its/MultiPartUploadIT.java
+++ b/integration-tests/src/test/java/com/adobe/testing/s3mock/its/MultiPartUploadIT.java
@@ -18,8 +18,10 @@ package com.adobe.testing.s3mock.its;
 
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import com.amazonaws.services.s3.model.AbortMultipartUploadRequest;
+import com.amazonaws.services.s3.model.AmazonS3Exception;
 import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest;
 import com.amazonaws.services.s3.model.CompleteMultipartUploadResult;
 import com.amazonaws.services.s3.model.CopyPartRequest;
@@ -471,6 +473,40 @@ public class MultiPartUploadIT extends S3TestBase {
     assertThat(partListing.getParts().get(0).getETag()).isEqualTo(copyPartResult.getETag());
   }
 
+  /**
+   * Tries to copy part of an non-existing object to a new bucket.
+   */
+  @Test
+  void shouldThrowNoSuchKeyOnCopyObjectPartForNonExistingKey() {
+    final String sourceKey = "NON_EXISTENT_KEY";
+    final String destinationBucketName = "destinationbucket";
+    final String destinationKey = "copyOf/" + sourceKey;
+    s3Client.createBucket(BUCKET_NAME);
+    s3Client.createBucket(destinationBucketName);
+
+    final ObjectMetadata objectMetadata = new ObjectMetadata();
+    objectMetadata.addUserMetadata("key", "value");
+
+    final InitiateMultipartUploadResult initiateMultipartUploadResult = s3Client
+        .initiateMultipartUpload(
+            new InitiateMultipartUploadRequest(destinationBucketName, destinationKey,
+                objectMetadata));
+    final String uploadId = initiateMultipartUploadResult.getUploadId();
+
+    CopyPartRequest copyPartRequest = new CopyPartRequest();
+    copyPartRequest.setDestinationBucketName(destinationBucketName);
+    copyPartRequest.setUploadId(uploadId);
+    copyPartRequest.setDestinationKey(destinationKey);
+    copyPartRequest.setSourceBucketName(BUCKET_NAME);
+    copyPartRequest.setSourceKey(sourceKey);
+    copyPartRequest.setFirstByte(0L);
+    copyPartRequest.setLastByte(5L);
+
+    assertThatThrownBy(() -> s3Client.copyPart(copyPartRequest))
+        .isInstanceOf(AmazonS3Exception.class)
+        .hasMessageContaining("Status Code: 404; Error Code: NoSuchKey");
+  }
+
   private PartETag uploadPart(String key, String uploadId, int partNumber, byte[] randomBytes) {
     return s3Client
         .uploadPart(createUploadPartRequest(key, uploadId)
diff --git a/server/src/main/java/com/adobe/testing/s3mock/FileStoreController.java b/server/src/main/java/com/adobe/testing/s3mock/FileStoreController.java
index 4708833b..4251e1ef 100644
--- a/server/src/main/java/com/adobe/testing/s3mock/FileStoreController.java
+++ b/server/src/main/java/com/adobe/testing/s3mock/FileStoreController.java
@@ -866,6 +866,7 @@ public class FileStoreController {
     verifyBucketExistence(destinationBucket);
 
     final String destinationFile = filenameFrom(destinationBucket, request);
+    verifyObjectExistence(copySource.getBucket(), copySource.getKey());
     final String partEtag = fileStore.copyPart(copySource.getBucket(),
         copySource.getKey(),
         copyRange,
@@ -1018,6 +1019,7 @@ public class FileStoreController {
           required = false) final String kmsKeyId,
       final HttpServletRequest request) throws IOException {
     verifyBucketExistence(destinationBucket);
+    verifyObjectExistence(objectRef.getBucket(), objectRef.getKey());
     final String destinationFile = filenameFrom(destinationBucket, request);
 
     final CopyObjectResult copyObjectResult;