Repository: fsouza/fake-gcs-server
Pull request #1251 (2023-07-17 18:19:07)

Remove obsolete upload route
POSTing to `/storage/v1/b/my-bucket/o` to upload an object is not supposed to work. The Google Cloud Storage API returns a 400 error with a response body like this:

```json
{
  "error": {
    "code": 400,
    "message": "Uploads must be sent to the upload URL. Re-send this request to https://storage.googleapis.com/upload/storage/v1/b/my-bucket/o?uploadType=media&name=test.txt",
    "errors": [
      {
        "message": "Uploads must be sent to the upload URL. Re-send this request to https://storage.googleapis.com/upload/storage/v1/b/my-bucket/o?uploadType=media&name=test.txt",
        "domain": "global",
        "reason": "wrongUrlForUpload"
      }
    ]
  }
}
```

To stay true to the GCS API, we remove support from the `fake-gcs-server` as well.


Patch:
diff --git a/fakestorage/object.go b/fakestorage/object.go
index 47994f2..047629d 100644
--- a/fakestorage/object.go
+++ b/fakestorage/object.go
@@ -1121,6 +1121,14 @@ func (s *Server) composeObject(r *http.Request) jsonResponse {
 		}
 	}
 
+	const maxComposeObjects = 32
+	if len(composeRequest.SourceObjects) > maxComposeObjects {
+		return jsonResponse{
+			status:       http.StatusBadRequest,
+			errorMessage: fmt.Sprintf("The number of source components provided (%d) exceeds the maximum (%d)", len(composeRequest.SourceObjects), maxComposeObjects),
+		}
+	}
+
 	sourceNames := make([]string, 0, len(composeRequest.SourceObjects))
 	for _, n := range composeRequest.SourceObjects {
 		sourceNames = append(sourceNames, n.Name)
diff --git a/fakestorage/server.go b/fakestorage/server.go
index d851685..faa5053 100644
--- a/fakestorage/server.go
+++ b/fakestorage/server.go
@@ -264,7 +264,6 @@ func (s *Server) buildMuxer() {
 		r.Path("/b/{bucketName}").Methods(http.MethodPatch).HandlerFunc(jsonToHTTPHandler(s.updateBucket))
 		r.Path("/b/{bucketName}").Methods(http.MethodDelete).HandlerFunc(jsonToHTTPHandler(s.deleteBucket))
 		r.Path("/b/{bucketName}/o").Methods(http.MethodGet).HandlerFunc(jsonToHTTPHandler(s.listObjects))
-		r.Path("/b/{bucketName}/o").Methods(http.MethodPost).HandlerFunc(jsonToHTTPHandler(s.insertObject))
 		r.Path("/b/{bucketName}/o/{objectName:.+}").Methods(http.MethodPatch).HandlerFunc(jsonToHTTPHandler(s.patchObject))
 		r.Path("/b/{bucketName}/o/{objectName:.+}/acl").Methods(http.MethodGet).HandlerFunc(jsonToHTTPHandler(s.listObjectACL))
 		r.Path("/b/{bucketName}/o/{objectName:.+}/acl").Methods(http.MethodPost).HandlerFunc(jsonToHTTPHandler(s.setObjectACL))


Test patch:
diff --git a/fakestorage/object_test.go b/fakestorage/object_test.go
index 38c8adf..26dc27c 100644
--- a/fakestorage/object_test.go
+++ b/fakestorage/object_test.go
@@ -1992,6 +1992,7 @@ func TestServiceClientComposeObject(t *testing.T) {
 			destObjectName    string
 			sourceObjectNames []string
 			expectedContent   string
+			expectedError     string
 		}{
 			{
 				"destination file doesn't exist",
@@ -1999,6 +2000,7 @@ func TestServiceClientComposeObject(t *testing.T) {
 				"files/some-file.txt",
 				[]string{"files/source1.txt", "files/source2.txt"},
 				source1Content + source2Content,
+				"",
 			},
 			{
 				"destination file already exists",
@@ -2006,6 +2008,7 @@ func TestServiceClientComposeObject(t *testing.T) {
 				"files/destination.txt",
 				[]string{"files/source1.txt", "files/source2.txt"},
 				source1Content + source2Content,
+				"",
 			},
 			{
 				"destination is a source",
@@ -2013,6 +2016,15 @@ func TestServiceClientComposeObject(t *testing.T) {
 				"files/source3.txt",
 				[]string{"files/source2.txt", "files/source3.txt"},
 				source2Content + source3Content,
+				"",
+			},
+			{
+				"too many objects at once",
+				"first-bucket",
+				"files/destination.txt",
+				[]string{"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33"},
+				"",
+				"googleapi: Error 400: The number of source components provided (33) exceeds the maximum (32)",
 			},
 		}
 		for _, test := range tests {
@@ -2033,6 +2045,9 @@ func TestServiceClientComposeObject(t *testing.T) {
 				composer.Metadata = map[string]string{"baz": "qux"}
 				attrs, err := composer.Run(context.TODO())
 				if err != nil {
+					if err.Error() == test.expectedError {
+						return
+					}
 					t.Fatal(err)
 				}
 
diff --git a/fakestorage/upload_test.go b/fakestorage/upload_test.go
index fe2ccef..79908fa 100644
--- a/fakestorage/upload_test.go
+++ b/fakestorage/upload_test.go
@@ -489,7 +489,7 @@ func TestServerClientSimpleUpload(t *testing.T) {
 
 	const data = "some nice content"
 	const contentType = "text/plain"
-	req, err := http.NewRequest("POST", server.URL()+"/storage/v1/b/other-bucket/o?uploadType=media&name=some/nice/object.txt", strings.NewReader(data))
+	req, err := http.NewRequest("POST", server.URL()+"/upload/storage/v1/b/other-bucket/o?uploadType=media&name=some/nice/object.txt", strings.NewReader(data))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -633,7 +633,7 @@ func TestServerClientUploadWithPredefinedAclPublicRead(t *testing.T) {
 	}
 	compressed := buf.Bytes()
 
-	req, err := http.NewRequest("POST", server.URL()+"/storage/v1/b/other-bucket/o?predefinedAcl=publicRead&uploadType=media&name=some/nice/object.txt&contentEncoding="+contentEncoding, bytes.NewReader(compressed))
+	req, err := http.NewRequest("POST", server.URL()+"/upload/storage/v1/b/other-bucket/o?predefinedAcl=publicRead&uploadType=media&name=some/nice/object.txt&contentEncoding="+contentEncoding, bytes.NewReader(compressed))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -690,7 +690,7 @@ func TestServerClientSimpleUploadNoName(t *testing.T) {
 	server.CreateBucketWithOpts(CreateBucketOpts{Name: "other-bucket"})
 
 	const data = "some nice content"
-	req, err := http.NewRequest("POST", server.URL()+"/storage/v1/b/other-bucket/o?uploadType=media", strings.NewReader(data))
+	req, err := http.NewRequest("POST", server.URL()+"/upload/storage/v1/b/other-bucket/o?uploadType=media", strings.NewReader(data))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -715,7 +715,7 @@ func TestServerInvalidUploadType(t *testing.T) {
 	defer server.Stop()
 	server.CreateBucketWithOpts(CreateBucketOpts{Name: "other-bucket"})
 	const data = "some nice content"
-	req, err := http.NewRequest("POST", server.URL()+"/storage/v1/b/other-bucket/o?uploadType=bananas&name=some-object.txt", strings.NewReader(data))
+	req, err := http.NewRequest("POST", server.URL()+"/upload/storage/v1/b/other-bucket/o?uploadType=bananas&name=some-object.txt", strings.NewReader(data))
 	if err != nil {
 		t.Fatal(err)
 	}