Repository: fsouza/fake-gcs-server
Pull request #1730 (2024-08-27 09:19:37)

Support setting cache-control of object

gavin726: Hi, @fsouza , The simulator is very cool! 
I use it in local unit testing. There is a scenario where I will set cache-control of object and need to get and check it. Currently, the simulator does not support it, so this PR supports setting cache-control. Please help review it when you are free, and if there is any problem with it, feel free to ping me, thank you!

Patch:
diff --git a/fakestorage/object.go b/fakestorage/object.go
index 58c9639..b229a45 100644
--- a/fakestorage/object.go
+++ b/fakestorage/object.go
@@ -35,6 +35,7 @@ type ObjectAttrs struct {
 	ContentType        string
 	ContentEncoding    string
 	ContentDisposition string
+	CacheControl       string
 	// Crc32c checksum of Content. calculated by server when it's upload methods are used.
 	Crc32c  string
 	Md5Hash string
@@ -396,6 +397,7 @@ func toBackendObjects(objects []StreamingObject) []backend.StreamingObject {
 				ContentType:        o.ContentType,
 				ContentEncoding:    o.ContentEncoding,
 				ContentDisposition: o.ContentDisposition,
+				CacheControl:       o.CacheControl,
 				ACL:                o.ACL,
 				Created:            getCurrentIfZero(o.Created).Format(timestampFormat),
 				Deleted:            o.Deleted.Format(timestampFormat),
@@ -450,6 +452,7 @@ func fromBackendObjects(objects []backend.StreamingObject) []StreamingObject {
 				ContentType:        o.ContentType,
 				ContentEncoding:    o.ContentEncoding,
 				ContentDisposition: o.ContentDisposition,
+				CacheControl:       o.CacheControl,
 				Crc32c:             o.Crc32c,
 				Md5Hash:            o.Md5Hash,
 				Etag:               o.Etag,
@@ -477,6 +480,7 @@ func fromBackendObjectsAttrs(objectAttrs []backend.ObjectAttrs) []ObjectAttrs {
 			ContentType:        o.ContentType,
 			ContentEncoding:    o.ContentEncoding,
 			ContentDisposition: o.ContentDisposition,
+			CacheControl:       o.CacheControl,
 			Crc32c:             o.Crc32c,
 			Md5Hash:            o.Md5Hash,
 			Etag:               o.Etag,
@@ -898,6 +902,9 @@ func (s *Server) downloadObject(w http.ResponseWriter, r *http.Request) {
 		if obj.ContentType != "" {
 			w.Header().Set(contentTypeHeader, obj.ContentType)
 		}
+		if obj.CacheControl != "" {
+			w.Header().Set(cacheControlHeader, obj.CacheControl)
+		}
 		// If content was transcoded, the underlying encoding was removed so we shouldn't report it.
 		if obj.ContentEncoding != "" && !transcoded {
 			w.Header().Set("Content-Encoding", obj.ContentEncoding)
diff --git a/fakestorage/upload.go b/fakestorage/upload.go
index 939ed29..e9181df 100644
--- a/fakestorage/upload.go
+++ b/fakestorage/upload.go
@@ -26,7 +26,10 @@ import (
 	"github.com/gorilla/mux"
 )
 
-const contentTypeHeader = "Content-Type"
+const (
+	contentTypeHeader  = "Content-Type"
+	cacheControlHeader = "Cache-Control"
+)
 
 const (
 	uploadTypeMedia     = "media"
@@ -51,6 +54,7 @@ type multipartMetadata struct {
 	ContentType        string            `json:"contentType"`
 	ContentEncoding    string            `json:"contentEncoding"`
 	ContentDisposition string            `json:"contentDisposition"`
+	CacheControl       string            `json:"cacheControl"`
 	CustomTime         time.Time         `json:"customTime,omitempty"`
 	Name               string            `json:"name"`
 	Metadata           map[string]string `json:"metadata"`
@@ -239,6 +243,7 @@ func (s *Server) simpleUpload(bucketName string, r *http.Request) jsonResponse {
 			BucketName:      bucketName,
 			Name:            name,
 			ContentType:     r.Header.Get(contentTypeHeader),
+			CacheControl:    r.Header.Get(cacheControlHeader),
 			ContentEncoding: contentEncoding,
 			CustomTime:      convertTimeWithoutError(customTime),
 			ACL:             getObjectACL(predefinedACL),
@@ -374,6 +379,7 @@ func (s *Server) multipartUpload(bucketName string, r *http.Request) jsonRespons
 			BucketName:         bucketName,
 			Name:               objName,
 			ContentType:        contentType,
+			CacheControl:       metadata.CacheControl,
 			ContentEncoding:    metadata.ContentEncoding,
 			ContentDisposition: metadata.ContentDisposition,
 			CustomTime:         metadata.CustomTime,
@@ -423,6 +429,7 @@ func (s *Server) resumableUpload(bucketName string, r *http.Request) jsonRespons
 			BucketName:      bucketName,
 			Name:            objName,
 			ContentType:     metadata.ContentType,
+			CacheControl:    metadata.CacheControl,
 			ContentEncoding: contentEncoding,
 			CustomTime:      metadata.CustomTime,
 			ACL:             getObjectACL(predefinedACL),
diff --git a/internal/backend/object.go b/internal/backend/object.go
index eb7956d..63bf8d6 100644
--- a/internal/backend/object.go
+++ b/internal/backend/object.go
@@ -21,6 +21,7 @@ type ObjectAttrs struct {
 	ContentType        string
 	ContentEncoding    string
 	ContentDisposition string
+	CacheControl       string
 	Crc32c             string
 	Md5Hash            string
 	Etag               string


Test patch:
diff --git a/fakestorage/upload_test.go b/fakestorage/upload_test.go
index 582c913..6e2e2d8 100644
--- a/fakestorage/upload_test.go
+++ b/fakestorage/upload_test.go
@@ -70,6 +70,7 @@ func TestServerClientObjectWriter(t *testing.T) {
 			test := test
 			t.Run(test.testCase, func(t *testing.T) {
 				const contentType = "text/plain; charset=utf-8"
+				cacheControl := "public, max-age=3600"
 				server.CreateBucketWithOpts(CreateBucketOpts{Name: test.bucketName})
 				client := server.Client()
 
@@ -82,6 +83,7 @@ func TestServerClientObjectWriter(t *testing.T) {
 				w.Metadata = map[string]string{
 					"foo": "bar",
 				}
+				w.CacheControl = cacheControl
 				w.Write([]byte(content))
 				err := w.Close()
 				if err != nil {
@@ -123,6 +125,9 @@ func TestServerClientObjectWriter(t *testing.T) {
 				if !customTime.Equal(obj.CustomTime) {
 					t.Errorf("wrong custom time\nwant %q\ngot  %q", customTime.String(), obj.CustomTime.String())
 				}
+				if obj.CacheControl != cacheControl {
+					t.Errorf("wrong cache control\nwant %q\ngot  %q", cacheControl, obj.CacheControl)
+				}
 
 				reader, err := client.Bucket(test.bucketName).Object(test.objectName).NewReader(context.Background())
 				if err != nil {
@@ -154,6 +159,7 @@ func TestServerClientObjectWriterOverwrite(t *testing.T) {
 	runServersTest(t, runServersOptions{}, func(t *testing.T, server *Server) {
 		const content = "other content"
 		const contentType = "text/plain"
+		const cacheControl = "no-cache"
 		server.CreateObject(Object{
 			ObjectAttrs: ObjectAttrs{
 				BucketName:  "some-bucket",
@@ -165,6 +171,7 @@ func TestServerClientObjectWriterOverwrite(t *testing.T) {
 		objHandle := server.Client().Bucket("some-bucket").Object("some-object.txt")
 		w := objHandle.NewWriter(context.Background())
 		w.ContentType = contentType
+		w.CacheControl = cacheControl
 		w.Write([]byte(content))
 		err := w.Close()
 		if err != nil {
@@ -181,6 +188,9 @@ func TestServerClientObjectWriterOverwrite(t *testing.T) {
 		if obj.ContentType != contentType {
 			t.Errorf("wrong content-type\nwsant %q\ngot  %q", contentType, obj.ContentType)
 		}
+		if obj.CacheControl != cacheControl {
+			t.Errorf("wrong cache control\nwant %q\ngot  %q", cacheControl, obj.CacheControl)
+		}
 	})
 }