Repository: fsouza/fake-gcs-server
Pull request #143 (2019-11-28 16:55:17)

Rewrite with generation support
_note: again incremental PR: the review and potential merge should go after https://github.com/fsouza/fake-gcs-server/pull/140_ 

An easy one: just looking and considering the optional query arg that may come with rewrite (copy) requests to specify an specific generation of an object. Tests decoupled from the ones that work without versioning neither specific generations, so we initialize the buckets accordingly. Extra test scenario considered for rewrite object (against the same active object)


Patch:
diff --git a/fakestorage/object.go b/fakestorage/object.go
index c1cb70a..251ea58 100644
--- a/fakestorage/object.go
+++ b/fakestorage/object.go
@@ -298,7 +298,22 @@ func (s *Server) setObjectACL(w http.ResponseWriter, r *http.Request) {
 
 func (s *Server) rewriteObject(w http.ResponseWriter, r *http.Request) {
 	vars := mux.Vars(r)
-	obj, err := s.GetObject(vars["sourceBucket"], vars["sourceObject"])
+	generationStr := r.FormValue("sourceGeneration")
+	var (
+		obj        Object
+		err        error
+		generation int64
+	)
+	if generationStr != "" {
+		generation, err = strconv.ParseInt(generationStr, 10, 64)
+		if err != nil {
+			http.Error(w, "Wrong generation ID", http.StatusBadRequest)
+			return
+		}
+		obj, err = s.GetObjectWithGeneration(vars["sourceBucket"], vars["sourceObject"], generation)
+	} else {
+		obj, err = s.GetObject(vars["sourceBucket"], vars["sourceObject"])
+	}
 	if err != nil {
 		http.Error(w, "not found", http.StatusNotFound)
 		return


Test patch:
diff --git a/fakestorage/object_test.go b/fakestorage/object_test.go
index 7e9f16e..e4f082a 100644
--- a/fakestorage/object_test.go
+++ b/fakestorage/object_test.go
@@ -867,6 +867,13 @@ func TestServiceClientRewriteObject(t *testing.T) {
 			crc32c     uint32
 			md5hash    string
 		}{
+			{
+				"same bucket same file",
+				"first-bucket",
+				"files/some-file.txt",
+				checksum,
+				encodedHash(hash),
+			},
 			{
 				"same bucket",
 				"first-bucket",
@@ -931,6 +938,129 @@ func TestServiceClientRewriteObject(t *testing.T) {
 	})
 }
 
+func TestServiceClientRewriteObjectWithGenerations(t *testing.T) {
+	const (
+		overwrittenContent    = "i was there"
+		overwrittenGeneration = 123
+		latestContent         = "some content"
+		contentType           = "text/plain; charset=utf-8"
+	)
+	objs := []Object{
+		{
+			BucketName:  "first-bucket",
+			Name:        "files/some-file.txt",
+			Content:     []byte(overwrittenContent),
+			ContentType: contentType,
+			Crc32c:      encodedChecksum(uint32ToBytes(uint32Checksum([]byte(overwrittenContent)))),
+			Md5Hash:     encodedHash(md5Hash([]byte(overwrittenContent))),
+			Generation:  overwrittenGeneration,
+		},
+		{
+			BucketName:  "first-bucket",
+			Name:        "files/some-file.txt",
+			Content:     []byte(latestContent),
+			ContentType: contentType,
+			Crc32c:      encodedChecksum(uint32ToBytes(uint32Checksum([]byte(latestContent)))),
+			Md5Hash:     encodedHash(md5Hash([]byte(latestContent))),
+		},
+	}
+	tests := []struct {
+		testCase   string
+		bucketName string
+		objectName string
+		versioning bool
+		expectErr  bool
+	}{
+		{
+			"same bucket from old gen",
+			"first-bucket",
+			"files/other-file.txt",
+			true,
+			false,
+		},
+		{
+			"same bucket same file from old gen",
+			"first-bucket",
+			"files/some-file.txt",
+			true,
+			false,
+		},
+		{
+			"different bucket",
+			"empty-bucket",
+			"some/interesting/file.txt",
+			true,
+			false,
+		},
+		{
+			"no versioning, no old gen",
+			"empty-bucket",
+			"some/interesting/file.txt",
+			false,
+			true,
+		},
+	}
+	for _, test := range tests {
+		test := test
+		t.Run(test.testCase, func(t *testing.T) {
+			runServersTest(t, []Object{}, func(t *testing.T, server *Server) {
+				server.CreateBucketWithOpts(CreateBucketOpts{Name: "empty-bucket", VersioningEnabled: false})
+				server.CreateBucketWithOpts(CreateBucketOpts{Name: "first-bucket", VersioningEnabled: test.versioning})
+				for _, obj := range objs {
+					server.CreateObject(obj)
+				}
+				client := server.Client()
+				sourceObject := client.Bucket("first-bucket").Object("files/some-file.txt").Generation(overwrittenGeneration)
+				expectedContent := overwrittenContent
+				expectedChecksum := uint32Checksum([]byte(overwrittenContent))
+				expectedHash := md5Hash([]byte(overwrittenContent))
+				dstObject := client.Bucket(test.bucketName).Object(test.objectName)
+				attrs, err := dstObject.CopierFrom(sourceObject).Run(context.TODO())
+				if err != nil {
+					if test.expectErr {
+						t.Skip("we were expecting an error for this test")
+					}
+					t.Fatal(err)
+				}
+				if attrs.Bucket != test.bucketName {
+					t.Errorf("wrong bucket in copied object attrs\nwant %q\ngot  %q", test.bucketName, attrs.Bucket)
+				}
+				if attrs.Name != test.objectName {
+					t.Errorf("wrong name in copied object attrs\nwant %q\ngot  %q", test.objectName, attrs.Name)
+				}
+				if attrs.Size != int64(len(expectedContent)) {
+					t.Errorf("wrong size in copied object attrs\nwant %d\ngot  %d", len(expectedContent), attrs.Size)
+				}
+				if attrs.CRC32C != expectedChecksum {
+					t.Errorf("wrong checksum in copied object attrs\nwant %d\ngot  %d", expectedChecksum, attrs.CRC32C)
+				}
+				if attrs.ContentType != contentType {
+					t.Errorf("wrong content type\nwant %q\ngot  %q", contentType, attrs.ContentType)
+				}
+				if !bytes.Equal(attrs.MD5, expectedHash) {
+					t.Errorf("wrong hash returned\nwant %d\ngot   %d", expectedHash, attrs.MD5)
+				}
+				obj, err := server.GetObject(test.bucketName, test.objectName)
+				if err != nil {
+					t.Fatal(err)
+				}
+				if string(obj.Content) != expectedContent {
+					t.Errorf("wrong content on object\nwant %q\ngot  %q", expectedContent, string(obj.Content))
+				}
+				if expect := encodedChecksum(uint32ToBytes(expectedChecksum)); expect != obj.Crc32c {
+					t.Errorf("wrong checksum on object\nwant %s\ngot  %s", expect, obj.Crc32c)
+				}
+				if expect := encodedHash(expectedHash); expect != obj.Md5Hash {
+					t.Errorf("wrong hash on object\nwant %s\ngot  %s", expect, obj.Md5Hash)
+				}
+				if obj.ContentType != contentType {
+					t.Errorf("wrong content type\nwant %q\ngot  %q", contentType, obj.ContentType)
+				}
+			})
+		})
+	}
+}
+
 func TestServerClientObjectDelete(t *testing.T) {
 	const (
 		bucketName = "some-bucket"