Repository: apigee/registry (go)

Issue:
`visitor.GetSpecs` should uncompress specs before returning them
As discussed here: https://github.com/apigee/registry/pull/1122#discussion_r1145601962

Pull request description:
Automatically uncompress all spec data returned by the visitor package.
This fixes #1123 by modifying `visitor.GetSpecs` (and the underlying  `visitor.FetchSpecContents`) to always return ungzipped specs. To avoid confusing downstream code, this also modifies the mime type of the returned specs to match the unzipped content (i.e. the "+gzip" is removed). This might be seen as misrepresenting the content stored on the server, but overall I think this is better because downstream code often switches on spec mime types, and this avoids accidental double-uncompression.

Moving this uncompression into the `visitor` package allows us to eliminate uncompression throughout the registry tool subcommands. Now any client code that uses `visitor` can safely expect gzipped specs to be uncompressed.

With a few other small changes, this eliminates `visitor.GetSpecBytes()`, replacing it with calls to `visitor.FetchSpecContents()`. `FetchSpecContents` is useful in situations where we are working on large numbers of specs and want to avoid loading all these specs into memory as we are putting them in task queues for processing. Instead we defer the "fetch" to when the specs are needed in the task handlers.

There's some untested code in `compute lint` that will be addressed in future PRs (#1121).

Patch:
diff --git a/cmd/registry/cmd/compute/complexity/complexity.go b/cmd/registry/cmd/compute/complexity/complexity.go
index 16f198f..9ee8b2d 100644
--- a/cmd/registry/cmd/compute/complexity/complexity.go
+++ b/cmd/registry/cmd/compute/complexity/complexity.go
@@ -17,9 +17,7 @@ package complexity
 import (
 	"context"
 	"fmt"
-	"strings"
 
-	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/tasks"
 	"github.com/apigee/registry/pkg/connection"
 	"github.com/apigee/registry/pkg/log"
@@ -133,11 +131,6 @@ func (task *computeComplexityTask) Run(ctx context.Context) error {
 	relation := "complexity"
 	log.Debugf(ctx, "Computing %s/artifacts/%s", task.specName, relation)
 	contents := spec.GetContents()
-	if strings.Contains(spec.GetMimeType(), "+gzip") {
-		if contents, err = compress.GUnzippedBytes(contents); err != nil {
-			return err
-		}
-	}
 	var complexity *metrics.Complexity
 	if mime.IsOpenAPIv2(spec.GetMimeType()) {
 		document, err := oas2.ParseDocument(contents)
diff --git a/cmd/registry/cmd/compute/lint/lint.go b/cmd/registry/cmd/compute/lint/lint.go
index 525f79a..7328992 100644
--- a/cmd/registry/cmd/compute/lint/lint.go
+++ b/cmd/registry/cmd/compute/lint/lint.go
@@ -74,10 +74,10 @@ func Command() *cobra.Command {
 			// Iterate through a collection of specs and evaluate each.
 			err = visitor.ListSpecs(ctx, client, spec, filter, false, func(ctx context.Context, spec *rpc.ApiSpec) error {
 				taskQueue <- &computeLintTask{
-					client:   client,
-					specName: spec.Name,
-					linter:   linter,
-					dryRun:   dryRun,
+					client: client,
+					spec:   spec,
+					linter: linter,
+					dryRun: dryRun,
 				}
 				return nil
 			})
@@ -92,14 +92,14 @@ func Command() *cobra.Command {
 }
 
 type computeLintTask struct {
-	client   connection.RegistryClient
-	specName string
-	linter   string
-	dryRun   bool
+	client connection.RegistryClient
+	spec   *rpc.ApiSpec
+	linter string
+	dryRun bool
 }
 
 func (task *computeLintTask) String() string {
-	return fmt.Sprintf("compute %s/lint-%s", task.specName, task.linter)
+	return fmt.Sprintf("compute %s/lint-%s", task.spec.Name, task.linter)
 }
 
 func lintRelation(linter string) string {
@@ -107,14 +107,8 @@ func lintRelation(linter string) string {
 }
 
 func (task *computeLintTask) Run(ctx context.Context) error {
-	request := &rpc.GetApiSpecRequest{
-		Name: task.specName,
-	}
-	spec, err := task.client.GetApiSpec(ctx, request)
-	if err != nil {
-		return err
-	}
-	data, err := visitor.GetBytesForSpec(ctx, task.client, spec)
+	spec := task.spec
+	err := visitor.FetchSpecContents(ctx, task.client, spec)
 	if err != nil {
 		return err
 	}
@@ -127,7 +121,7 @@ func (task *computeLintTask) Run(ctx context.Context) error {
 		}
 		relation = lintRelation(task.linter)
 		log.Debugf(ctx, "Computing %s/artifacts/%s", spec.Name, relation)
-		lint, err = NewLintFromOpenAPI(spec.Name, data, task.linter)
+		lint, err = NewLintFromOpenAPI(spec.Name, spec.Contents, task.linter)
 		if err != nil {
 			return fmt.Errorf("error processing OpenAPI: %s (%s)", spec.Name, err.Error())
 		}
@@ -140,7 +134,7 @@ func (task *computeLintTask) Run(ctx context.Context) error {
 		}
 		relation = lintRelation(task.linter)
 		log.Debugf(ctx, "Computing %s/artifacts/%s", spec.Name, relation)
-		lint, err = NewLintFromZippedProtos(spec.Name, data)
+		lint, err = NewLintFromZippedProtos(spec.Name, spec.Contents)
 		if err != nil {
 			return fmt.Errorf("error processing protos: %s (%s)", spec.Name, err.Error())
 		}
diff --git a/cmd/registry/cmd/compute/vocabulary/vocabulary.go b/cmd/registry/cmd/compute/vocabulary/vocabulary.go
index e962d4a..d6eb761 100644
--- a/cmd/registry/cmd/compute/vocabulary/vocabulary.go
+++ b/cmd/registry/cmd/compute/vocabulary/vocabulary.go
@@ -18,7 +18,6 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/tasks"
 	"github.com/apigee/registry/pkg/connection"
 	"github.com/apigee/registry/pkg/log"
@@ -28,7 +27,6 @@ import (
 	"github.com/apigee/registry/rpc"
 	"github.com/google/gnostic/metrics/vocabulary"
 	"github.com/spf13/cobra"
-	"google.golang.org/grpc/metadata"
 	"google.golang.org/protobuf/encoding/protojson"
 	"google.golang.org/protobuf/proto"
 
@@ -81,18 +79,18 @@ func Command() *cobra.Command {
 			if parsed.RevisionID == "" {
 				err = visitor.ListSpecs(ctx, client, parsed.Spec(), filter, false, func(ctx context.Context, spec *rpc.ApiSpec) error {
 					taskQueue <- &computeVocabularyTask{
-						client:   client,
-						specName: spec.GetName(),
-						dryRun:   dryRun,
+						client: client,
+						spec:   spec,
+						dryRun: dryRun,
 					}
 					return nil
 				})
 			} else {
 				err = visitor.ListSpecRevisions(ctx, client, parsed, filter, false, func(ctx context.Context, spec *rpc.ApiSpec) error {
 					taskQueue <- &computeVocabularyTask{
-						client:   client,
-						specName: spec.GetName(),
-						dryRun:   dryRun,
+						client: client,
+						spec:   spec,
+						dryRun: dryRun,
 					}
 					return nil
 				})
@@ -109,62 +107,53 @@ func Command() *cobra.Command {
 }
 
 type computeVocabularyTask struct {
-	client   connection.RegistryClient
-	specName string
-	dryRun   bool
+	client connection.RegistryClient
+	spec   *rpc.ApiSpec
+	dryRun bool
 }
 
 func (task *computeVocabularyTask) String() string {
-	return "compute vocabulary " + task.specName
+	return "compute vocabulary " + task.spec.Name
 }
 
 func (task *computeVocabularyTask) Run(ctx context.Context) error {
-	ctx = metadata.AppendToOutgoingContext(ctx, "accept-encoding", "gzip")
-	contents, err := task.client.GetApiSpecContents(ctx, &rpc.GetApiSpecContentsRequest{
-		Name: task.specName,
-	})
-	if err != nil {
+	if err := visitor.FetchSpecContents(ctx, task.client, task.spec); err != nil {
 		return err
 	}
-	if mime.IsGZipCompressed(contents.ContentType) {
-		contents.Data, err = compress.GUnzippedBytes(contents.Data)
-		if err != nil {
-			return err
-		}
-	}
 
-	log.Debugf(ctx, "Computing %s/artifacts/vocabulary", task.specName)
+	log.Debugf(ctx, "Computing %s/artifacts/vocabulary", task.spec.Name)
 	var vocab *metrics.Vocabulary
 
-	if mime.IsOpenAPIv2(contents.GetContentType()) {
-		document, err := oas2.ParseDocument(contents.GetData())
+	if mime.IsOpenAPIv2(task.spec.GetMimeType()) {
+		document, err := oas2.ParseDocument(task.spec.GetContents())
 		if err != nil {
-			log.FromContext(ctx).WithError(err).Errorf("Invalid OpenAPI: %s", task.specName)
+			log.FromContext(ctx).WithError(err).Errorf("Invalid OpenAPI: %s", task.spec.Name)
 			return nil
 		}
 		vocab = vocabulary.NewVocabularyFromOpenAPIv2(document)
-	} else if mime.IsOpenAPIv3(contents.GetContentType()) {
-		document, err := oas3.ParseDocument(contents.GetData())
+	} else if mime.IsOpenAPIv3(task.spec.GetMimeType()) {
+		document, err := oas3.ParseDocument(task.spec.GetContents())
 		if err != nil {
-			log.FromContext(ctx).WithError(err).Errorf("Invalid OpenAPI: %s", task.specName)
+			log.FromContext(ctx).WithError(err).Errorf("Invalid OpenAPI: %s", task.spec.Name)
 			return nil
 		}
 		vocab = vocabulary.NewVocabularyFromOpenAPIv3(document)
-	} else if mime.IsDiscovery(contents.GetContentType()) {
-		document, err := discovery.ParseDocument(contents.GetData())
+	} else if mime.IsDiscovery(task.spec.GetMimeType()) {
+		document, err := discovery.ParseDocument(task.spec.GetContents())
 		if err != nil {
-			log.FromContext(ctx).WithError(err).Errorf("Invalid Discovery: %s", task.specName)
+			log.FromContext(ctx).WithError(err).Errorf("Invalid Discovery: %s", task.spec.Name)
 			return nil
 		}
 		vocab = vocabulary.NewVocabularyFromDiscovery(document)
-	} else if mime.IsProto(contents.GetContentType()) && mime.IsZipArchive(contents.GetContentType()) {
-		vocab, err = NewVocabularyFromZippedProtos(contents.GetData())
+	} else if mime.IsProto(task.spec.GetMimeType()) && mime.IsZipArchive(task.spec.GetMimeType()) {
+		var err error
+		vocab, err = NewVocabularyFromZippedProtos(task.spec.GetContents())
 		if err != nil {
-			log.FromContext(ctx).WithError(err).Errorf("Error processing protos: %s", task.specName)
+			log.FromContext(ctx).WithError(err).Errorf("Error processing protos: %s", task.spec.Name)
 			return nil
 		}
 	} else {
-		return fmt.Errorf("we don't know how to compute the vocabulary of %s", task.specName)
+		return fmt.Errorf("we don't know how to compute the vocabulary of %s", task.spec.Name)
 	}
 
 	if task.dryRun {
@@ -177,7 +166,7 @@ func (task *computeVocabularyTask) Run(ctx context.Context) error {
 		return err
 	}
 	return visitor.SetArtifact(ctx, task.client, &rpc.Artifact{
-		Name:     task.specName + "/artifacts/vocabulary",
+		Name:     task.spec.Name + "/artifacts/vocabulary",
 		MimeType: mime.MimeTypeForMessageType("gnostic.metrics.Vocabulary"),
 		Contents: messageData,
 	})
diff --git a/cmd/registry/cmd/diff/diff.go b/cmd/registry/cmd/diff/diff.go
index e2c38a7..a672783 100644
--- a/cmd/registry/cmd/diff/diff.go
+++ b/cmd/registry/cmd/diff/diff.go
@@ -23,7 +23,6 @@ import (
 	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/pkg/connection"
 	"github.com/apigee/registry/pkg/log"
-	"github.com/apigee/registry/pkg/mime"
 	"github.com/apigee/registry/pkg/names"
 	"github.com/apigee/registry/pkg/visitor"
 	"github.com/apigee/registry/rpc"
@@ -196,22 +195,7 @@ func printDiff(spec1, spec2 *rpc.ApiSpec) error {
 			}
 		}
 	} else {
-		var err error
-		contents1 := spec1.Contents
-		if mime.IsGZipCompressed(spec1.MimeType) {
-			contents1, err = compress.GUnzippedBytes(contents1)
-			if err != nil {
-				return err
-			}
-		}
-		contents2 := spec2.Contents
-		if mime.IsGZipCompressed(spec2.MimeType) {
-			contents2, err = compress.GUnzippedBytes(contents2)
-			if err != nil {
-				return err
-			}
-		}
-		diff := computeDiff(contents1, contents2, spec1.Name, spec2.Name)
+		diff := computeDiff(spec1.Contents, spec2.Contents, spec1.Name, spec2.Name)
 		if len(diff) > 0 {
 			fmt.Println(diff)
 		}
diff --git a/cmd/registry/cmd/get/get.go b/cmd/registry/cmd/get/get.go
index 0609b04..9502809 100644
--- a/cmd/registry/cmd/get/get.go
+++ b/cmd/registry/cmd/get/get.go
@@ -19,9 +19,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"strings"
 
-	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/patch"
 	"github.com/apigee/registry/pkg/connection"
 	"github.com/apigee/registry/pkg/encoding"
@@ -221,11 +219,7 @@ func (v *getVisitor) SpecHandler() visitor.SpecHandler {
 			if err := visitor.FetchSpecContents(ctx, v.registryClient, message); err != nil {
 				return err
 			}
-			contents := message.GetContents()
-			if strings.Contains(message.GetMimeType(), "+gzip") {
-				contents, _ = compress.GUnzippedBytes(contents)
-			}
-			v.results = append(v.results, contents)
+			v.results = append(v.results, message.GetContents())
 			return nil
 		case "yaml":
 			spec, err := patch.NewApiSpec(ctx, v.registryClient, message, v.nested)
diff --git a/cmd/registry/conformance/conformance-task.go b/cmd/registry/conformance/conformance-task.go
index ba2a007..bf9df1d 100644
--- a/cmd/registry/conformance/conformance-task.go
+++ b/cmd/registry/conformance/conformance-task.go
@@ -94,7 +94,7 @@ func (task *ComputeConformanceTask) String() string {
 func (task *ComputeConformanceTask) Run(ctx context.Context) error {
 	log.Debugf(ctx, "Computing conformance report %s/artifacts/%s", task.Spec.GetName(), conformanceReportId(task.StyleguideId))
 
-	data, err := visitor.GetBytesForSpec(ctx, task.Client, task.Spec)
+	err := visitor.FetchSpecContents(ctx, task.Client, task.Spec)
 	if err != nil {
 		return err
 	}
@@ -111,10 +111,10 @@ func (task *ComputeConformanceTask) Run(ctx context.Context) error {
 	defer os.RemoveAll(root)
 
 	if mime.IsZipArchive(task.Spec.GetMimeType()) {
-		_, err = compress.UnzipArchiveToPath(data, root)
+		_, err = compress.UnzipArchiveToPath(task.Spec.GetContents(), root)
 	} else {
 		// Write the file to the temporary directory.
-		err = os.WriteFile(filepath.Join(root, name), data, 0644)
+		err = os.WriteFile(filepath.Join(root, name), task.Spec.GetContents(), 0644)
 	}
 	if err != nil {
 		return err
diff --git a/cmd/registry/patch/project.go b/cmd/registry/patch/project.go
index e9b13a9..972d8f7 100644
--- a/cmd/registry/patch/project.go
+++ b/cmd/registry/patch/project.go
@@ -20,9 +20,7 @@ import (
 	"io/fs"
 	"os"
 	"path/filepath"
-	"strings"
 
-	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/tasks"
 	"github.com/apigee/registry/gapic"
 	"github.com/apigee/registry/pkg/connection"
@@ -31,7 +29,6 @@ import (
 	"github.com/apigee/registry/pkg/names"
 	"github.com/apigee/registry/pkg/visitor"
 	"github.com/apigee/registry/rpc"
-	"google.golang.org/grpc/metadata"
 	"gopkg.in/yaml.v3"
 )
 
@@ -489,18 +486,11 @@ func (task *exportSpecTask) Run(ctx context.Context) error {
 	if task.message.Filename == "" {
 		return nil
 	}
-	ctx = metadata.AppendToOutgoingContext(ctx, "accept-encoding", "gzip")
-	contents, err := task.client.GetApiSpecContents(ctx, &rpc.GetApiSpecContentsRequest{
-		Name: task.message.GetName(),
-	})
+	err = visitor.FetchSpecContents(ctx, task.client, task.message)
 	if err != nil {
 		return err
 	}
-	data := contents.GetData()
-	if strings.Contains(contents.GetContentType(), "+gzip") {
-		data, _ = compress.GUnzippedBytes(data)
-	}
-	return os.WriteFile(filepath.Join(parentDir, task.message.Filename), data, 0644)
+	return os.WriteFile(filepath.Join(parentDir, task.message.Filename), task.message.GetContents(), 0644)
 }
 
 type exportDeploymentTask struct {
diff --git a/pkg/mime/types.go b/pkg/mime/types.go
index c340648..988b236 100644
--- a/pkg/mime/types.go
+++ b/pkg/mime/types.go
@@ -69,6 +69,11 @@ func IsGZipCompressed(mimeType string) bool {
 	return strings.Contains(mimeType, "+gzip")
 }
 
+// GUnzippedType returns an equivalent uncompressed MIME type for a compressed type.
+func GUnzippedType(mimeType string) string {
+	return strings.Replace(mimeType, "+gzip", "", 1)
+}
+
 // IsZipArchive returns true if a MIME type represents a type stored as a multifile Zip archive.
 func IsZipArchive(mimeType string) bool {
 	return strings.Contains(mimeType, "+zip")
diff --git a/pkg/visitor/content.go b/pkg/visitor/content.go
index cb1eec7..86e0cc2 100644
--- a/pkg/visitor/content.go
+++ b/pkg/visitor/content.go
@@ -18,13 +18,9 @@ import (
 	"context"
 	"path"
 
-	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/gapic"
-	"github.com/apigee/registry/pkg/connection"
-	"github.com/apigee/registry/pkg/mime"
 	"github.com/apigee/registry/rpc"
 	"google.golang.org/grpc/codes"
-	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/status"
 )
 
@@ -45,21 +41,7 @@ func SetArtifact(ctx context.Context,
 	if code == codes.AlreadyExists {
 		request := &rpc.ReplaceArtifactRequest{}
 		request.Artifact = artifact
-		_, err := client.ReplaceArtifact(ctx, request)
-		return err
+		_, err = client.ReplaceArtifact(ctx, request)
 	}
 	return err
 }
-
-func GetBytesForSpec(ctx context.Context, client connection.RegistryClient, spec *rpc.ApiSpec) ([]byte, error) {
-	request := &rpc.GetApiSpecContentsRequest{Name: spec.GetName()}
-	ctx = metadata.AppendToOutgoingContext(ctx, "accept-encoding", "gzip")
-	contents, err := client.GetApiSpecContents(ctx, request)
-	if err != nil {
-		return nil, err
-	}
-	if mime.IsGZipCompressed(contents.ContentType) {
-		return compress.GUnzippedBytes(contents.Data)
-	}
-	return contents.Data, nil
-}
diff --git a/pkg/visitor/fetch.go b/pkg/visitor/fetch.go
index 856cfc8..6ffa993 100644
--- a/pkg/visitor/fetch.go
+++ b/pkg/visitor/fetch.go
@@ -17,7 +17,9 @@ package visitor
 import (
 	"context"
 
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/gapic"
+	"github.com/apigee/registry/pkg/mime"
 	"github.com/apigee/registry/rpc"
 	"google.golang.org/grpc/metadata"
 )
@@ -34,9 +36,13 @@ func FetchSpecContents(ctx context.Context, client *gapic.RegistryClient, spec *
 	if err != nil {
 		return err
 	}
-	spec.Contents = contents.GetData()
 	spec.MimeType = contents.GetContentType()
-	return nil
+	spec.Contents = contents.GetData()
+	if mime.IsGZipCompressed(spec.MimeType) {
+		spec.MimeType = mime.GUnzippedType(spec.MimeType)
+		spec.Contents, err = compress.GUnzippedBytes(spec.Contents)
+	}
+	return err
 }
 
 func FetchArtifactContents(ctx context.Context, client *gapic.RegistryClient, artifact *rpc.Artifact) error {
diff --git a/pkg/visitor/list.go b/pkg/visitor/list.go
index eb0907b..9f2ea4e 100644
--- a/pkg/visitor/list.go
+++ b/pkg/visitor/list.go
@@ -198,6 +198,7 @@ func ListSpecs(ctx context.Context,
 			}
 			r.Contents = resp.GetData()
 			if mime.IsGZipCompressed(resp.ContentType) {
+				r.MimeType = mime.GUnzippedType(r.MimeType)
 				r.Contents, err = compress.GUnzippedBytes(r.Contents)
 				if err != nil {
 					return err
@@ -236,6 +237,7 @@ func ListSpecRevisions(ctx context.Context,
 			}
 			r.Contents = resp.GetData()
 			if mime.IsGZipCompressed(resp.ContentType) {
+				r.MimeType = mime.GUnzippedType(r.MimeType)
 				r.Contents, err = compress.GUnzippedBytes(r.Contents)
 				if err != nil {
 					return err


Test patch:
diff --git a/pkg/mime/types_test.go b/pkg/mime/types_test.go
index dd864d1..44fdbb7 100644
--- a/pkg/mime/types_test.go
+++ b/pkg/mime/types_test.go
@@ -72,6 +72,12 @@ func TestOpenAPIMimeTypes(t *testing.T) {
 			if IsZipArchive(value) {
 				t.Errorf("%s is incorrectly recognized as a zip archive", value)
 			}
+			if IsGZipCompressed(value) {
+				unzipped := GUnzippedType(value)
+				if IsGZipCompressed(unzipped) {
+					t.Errorf("failed to remove compression from type %q", value)
+				}
+			}
 		})
 	}
 }
diff --git a/pkg/visitor/content_test.go b/pkg/visitor/content_test.go
index 32b29d0..1df0202 100644
--- a/pkg/visitor/content_test.go
+++ b/pkg/visitor/content_test.go
@@ -19,7 +19,6 @@ import (
 	"context"
 	"testing"
 
-	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/pkg/connection/grpctest"
 	"github.com/apigee/registry/pkg/names"
 
@@ -32,25 +31,13 @@ func TestContentHelpers(t *testing.T) {
 	if err != nil {
 		t.Fatalf("failed to parse spec name %s", specName)
 	}
-	compressedSpecName, err := names.ParseSpec("projects/content-test/locations/global/apis/a/versions/v/specs/s-gzip")
-	if err != nil {
-		t.Fatalf("failed to parse spec name %s", compressedSpecName)
-	}
 	specContents := "hello"
-	compressedSpecContents, err := compress.GZippedBytes([]byte(specContents))
-	if err != nil {
-		t.Fatalf("failed to compress contents for %s", compressedSpecName)
-	}
 	ctx := context.Background()
 	registryClient, adminClient := grpctest.SetupRegistry(ctx, t, "content-test", []seeder.RegistryResource{
 		&rpc.ApiSpec{
 			Name:     specName.String(),
 			MimeType: "text/plain",
 			Contents: []byte(specContents)},
-		&rpc.ApiSpec{
-			Name:     compressedSpecName.String(),
-			MimeType: "text/plain+gzip",
-			Contents: compressedSpecContents},
 	})
 	t.Cleanup(func() {
 		if err := adminClient.DeleteProject(ctx, &rpc.DeleteProjectRequest{
@@ -60,30 +47,6 @@ func TestContentHelpers(t *testing.T) {
 			t.Fatalf("failed to delete test project: %s", err)
 		}
 	})
-	t.Run("fetch-spec-contents", func(t *testing.T) {
-		bytes, err := GetBytesForSpec(ctx, registryClient, &rpc.ApiSpec{Name: specName.String()})
-		if err != nil {
-			t.Fatalf("GetBytesForSpec failed to read spec contents")
-		}
-		if string(bytes) != specContents {
-			t.Fatalf("GetBytesForSpec returned incorrect spec contents (%q expected %q)", string(bytes), specContents)
-		}
-	})
-	t.Run("fetch-compressed-spec-contents", func(t *testing.T) {
-		bytes, err := GetBytesForSpec(ctx, registryClient, &rpc.ApiSpec{Name: compressedSpecName.String()})
-		if err != nil {
-			t.Fatalf("GetBytesForSpec failed to read spec contents")
-		}
-		if string(bytes) != specContents {
-			t.Fatalf("GetBytesForSpec returned incorrect spec contents (%q expected %q)", string(bytes), specContents)
-		}
-	})
-	t.Run("fetch-spec-contents-invalid", func(t *testing.T) {
-		_, err := GetBytesForSpec(ctx, registryClient, &rpc.ApiSpec{Name: specName.String() + "-invalid"})
-		if err == nil {
-			t.Fatalf("GetBytesForSpec of invalid spec succeeded and should have failed")
-		}
-	})
 	t.Run("set-artifact", func(t *testing.T) {
 		artifactName := specName.Artifact("x")
 		err := SetArtifact(ctx, registryClient, &rpc.Artifact{
diff --git a/pkg/visitor/fetch_test.go b/pkg/visitor/fetch_test.go
index 2930f2b..2bf1faf 100644
--- a/pkg/visitor/fetch_test.go
+++ b/pkg/visitor/fetch_test.go
@@ -30,8 +30,8 @@ func TestFetch(t *testing.T) {
 	project := names.Project{ProjectID: projectID}
 	parent := project.String() + "/locations/global"
 
-	specContents := "hello"
-	gzippedSpecContents, err := compress.GZippedBytes([]byte(specContents))
+	specContents := []byte("hello")
+	gzippedSpecContents, err := compress.GZippedBytes(specContents)
 	if err != nil {
 		t.Fatalf("Setup: Failed to compress test data: %s", err)
 	}
@@ -97,7 +97,7 @@ func TestFetch(t *testing.T) {
 		if err != nil {
 			t.Fatalf("Failed to fetch spec contents: %s", err)
 		}
-		if !bytes.Equal(spec.Contents, gzippedSpecContents) {
+		if !bytes.Equal(spec.Contents, specContents) {
 			t.Fatalf("Fetched unexpected spec contents: wanted %q got %q", specContents, spec.Contents)
 		}
 	})
@@ -117,7 +117,7 @@ func TestFetch(t *testing.T) {
 			t.Fatalf("Failed to parse spec name: %s", err)
 		}
 		err = GetSpec(ctx, registryClient, specName, true, func(ctx context.Context, spec *rpc.ApiSpec) error {
-			if !bytes.Equal(spec.Contents, gzippedSpecContents) {
+			if !bytes.Equal(spec.Contents, specContents) {
 				t.Fatalf("Fetched unexpected spec contents: wanted %q got %q", specContents, spec.Contents)
 			}
 			return nil
@@ -134,7 +134,7 @@ func TestFetch(t *testing.T) {
 		count := 0
 		err = ListSpecs(ctx, registryClient, specName, "", true, func(ctx context.Context, spec *rpc.ApiSpec) error {
 			count++
-			if string(spec.Contents) != specContents {
+			if !bytes.Equal(spec.Contents, specContents) {
 				t.Fatalf("Fetched unexpected spec contents: wanted %q got %q", specContents, spec.Contents)
 			}
 			return nil
@@ -152,7 +152,7 @@ func TestFetch(t *testing.T) {
 			t.Fatalf("Failed to parse spec revision name: %s", err)
 		}
 		err = GetSpecRevision(ctx, registryClient, specRevisionName, true, func(ctx context.Context, spec *rpc.ApiSpec) error {
-			if !bytes.Equal(spec.Contents, gzippedSpecContents) {
+			if !bytes.Equal(spec.Contents, specContents) {
 				t.Fatalf("Fetched unexpected spec contents: wanted %q got %q", specContents, spec.Contents)
 			}
 			return nil
@@ -169,7 +169,7 @@ func TestFetch(t *testing.T) {
 		count := 0
 		err = ListSpecRevisions(ctx, registryClient, specRevisionName, "", true, func(ctx context.Context, spec *rpc.ApiSpec) error {
 			count++
-			if string(spec.Contents) != specContents {
+			if !bytes.Equal(spec.Contents, specContents) {
 				t.Fatalf("Fetched unexpected spec contents: wanted %q got %q", specContents, spec.Contents)
 			}
 			return nil