Repository: apigee/registry
Pull request #1015 (2023-02-10 00:03:17)

Move compression functions from `core` to a new package named `compress`
Another step toward #1007

codecov[bot]: # [Codecov](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) Report
> Merging [#1015](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) (b844531) into [main](https://codecov.io/gh/apigee/registry/commit/d0cf78acc3fffcfdec14c7fe8dd17c685f42ed39?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) (d0cf78a) will **decrease** coverage by `0.65%`.
> The diff coverage is `61.53%`.

```diff
@@            Coverage Diff             @@
##             main    #1015      +/-   ##
==========================================
- Coverage   68.06%   67.42%   -0.65%     
==========================================
  Files         141      143       +2     
  Lines       11640    11769     +129     
==========================================
+ Hits         7923     7935      +12     
- Misses       3050     3165     +115     
- Partials      667      669       +2     
```


| [Impacted Files](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) | Coverage Δ | |
|---|---|---|
| [cmd/registry/cmd/compute/complexity/complexity.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NtZC9jb21wdXRlL2NvbXBsZXhpdHkvY29tcGxleGl0eS5nbw==) | `42.40% <0.00%> (ø)` | |
| [cmd/registry/cmd/compute/lint/lint-proto.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NtZC9jb21wdXRlL2xpbnQvbGludC1wcm90by5nbw==) | `0.00% <0.00%> (ø)` | |
| [cmd/registry/compress/gzip.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NvbXByZXNzL2d6aXAuZ28=) | `66.66% <ø> (ø)` | |
| [cmd/registry/compress/zip.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NvbXByZXNzL3ppcC5nbw==) | `0.00% <ø> (ø)` | |
| [cmd/registry/conformance/conformance-task.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NvbmZvcm1hbmNlL2NvbmZvcm1hbmNlLXRhc2suZ28=) | `46.55% <0.00%> (ø)` | |
| [cmd/registry/patch/spec.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L3BhdGNoL3NwZWMuZ28=) | `49.63% <50.00%> (ø)` | |
| [cmd/registry/cmd/get/get.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NtZC9nZXQvZ2V0Lmdv) | `80.59% <100.00%> (ø)` | |
| [cmd/registry/cmd/upload/csv.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NtZC91cGxvYWQvY3N2Lmdv) | `71.95% <100.00%> (ø)` | |
| [cmd/registry/cmd/upload/discovery.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NtZC91cGxvYWQvZGlzY292ZXJ5Lmdv) | `65.03% <100.00%> (ø)` | |
| [cmd/registry/cmd/upload/openapi.go](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NtZC91cGxvYWQvb3BlbmFwaS5nbw==) | `68.87% <100.00%> (ø)` | |
| ... and [4 more](https://codecov.io/gh/apigee/registry/pull/1015?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee)


Patch:
diff --git a/cmd/registry/cmd/compute/complexity/complexity.go b/cmd/registry/cmd/compute/complexity/complexity.go
index 3f975f4..6e7b398 100644
--- a/cmd/registry/cmd/compute/complexity/complexity.go
+++ b/cmd/registry/cmd/compute/complexity/complexity.go
@@ -19,6 +19,7 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/core"
 	"github.com/apigee/registry/cmd/registry/types"
 	"github.com/apigee/registry/log"
@@ -133,7 +134,7 @@ func (task *computeComplexityTask) Run(ctx context.Context) error {
 	log.Debugf(ctx, "Computing %s/artifacts/%s", task.specName, relation)
 	contents := spec.GetContents()
 	if strings.Contains(spec.GetMimeType(), "+gzip") {
-		if contents, err = core.GUnzippedBytes(contents); err != nil {
+		if contents, err = compress.GUnzippedBytes(contents); err != nil {
 			return err
 		}
 	}
diff --git a/cmd/registry/cmd/compute/lint/lint-proto.go b/cmd/registry/cmd/compute/lint/lint-proto.go
index a010fa6..c69d14f 100644
--- a/cmd/registry/cmd/compute/lint/lint-proto.go
+++ b/cmd/registry/cmd/compute/lint/lint-proto.go
@@ -20,7 +20,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/apigee/registry/cmd/registry/core"
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/rpc"
 	"google.golang.org/protobuf/encoding/protojson"
 )
@@ -35,7 +35,7 @@ func NewLintFromZippedProtos(name string, b []byte) (*rpc.Lint, error) {
 	// whenever we finish, delete the tmp directory
 	defer os.RemoveAll(root)
 	// unzip the protos to the temp directory
-	_, err = core.UnzipArchiveToPath(b, root+"/protos")
+	_, err = compress.UnzipArchiveToPath(b, root+"/protos")
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/registry/cmd/diff/diff.go b/cmd/registry/cmd/diff/diff.go
index 5de37f4..2b0e9af 100644
--- a/cmd/registry/cmd/diff/diff.go
+++ b/cmd/registry/cmd/diff/diff.go
@@ -20,7 +20,7 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/apigee/registry/cmd/registry/core"
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/types"
 	"github.com/apigee/registry/log"
 	"github.com/apigee/registry/pkg/connection"
@@ -157,11 +157,11 @@ func printDiff(spec1, spec2 *rpc.ApiSpec) error {
 	}
 	if strings.Contains(spec1.MimeType, "+zip") {
 		// read both zip archives into a map
-		map1, err := core.UnzipArchiveToMap(spec1.Contents)
+		map1, err := compress.UnzipArchiveToMap(spec1.Contents)
 		if err != nil {
 			return err
 		}
-		map2, err := core.UnzipArchiveToMap(spec2.Contents)
+		map2, err := compress.UnzipArchiveToMap(spec2.Contents)
 		if err != nil {
 			return err
 		}
@@ -199,14 +199,14 @@ func printDiff(spec1, spec2 *rpc.ApiSpec) error {
 		var err error
 		contents1 := spec1.Contents
 		if types.IsGZipCompressed(spec1.MimeType) {
-			contents1, err = core.GUnzippedBytes(contents1)
+			contents1, err = compress.GUnzippedBytes(contents1)
 			if err != nil {
 				return err
 			}
 		}
 		contents2 := spec2.Contents
 		if types.IsGZipCompressed(spec2.MimeType) {
-			contents2, err = core.GUnzippedBytes(contents2)
+			contents2, err = compress.GUnzippedBytes(contents2)
 			if err != nil {
 				return err
 			}
diff --git a/cmd/registry/cmd/get/get.go b/cmd/registry/cmd/get/get.go
index b910794..6014133 100644
--- a/cmd/registry/cmd/get/get.go
+++ b/cmd/registry/cmd/get/get.go
@@ -21,7 +21,7 @@ import (
 	"io"
 	"strings"
 
-	"github.com/apigee/registry/cmd/registry/core"
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/patch"
 	"github.com/apigee/registry/log"
 	"github.com/apigee/registry/pkg/connection"
@@ -194,7 +194,7 @@ func (v *getVisitor) SpecHandler() visitor.SpecHandler {
 			}
 			contents := message.GetContents()
 			if strings.Contains(message.GetMimeType(), "+gzip") {
-				contents, _ = core.GUnzippedBytes(contents)
+				contents, _ = compress.GUnzippedBytes(contents)
 			}
 			v.results = append(v.results, contents)
 			return nil
diff --git a/cmd/registry/cmd/upload/csv.go b/cmd/registry/cmd/upload/csv.go
index d61ac2b..3991d82 100644
--- a/cmd/registry/cmd/upload/csv.go
+++ b/cmd/registry/cmd/upload/csv.go
@@ -22,6 +22,7 @@ import (
 	"io"
 	"os"
 
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/core"
 	"github.com/apigee/registry/cmd/registry/types"
 	"github.com/apigee/registry/log"
@@ -224,7 +225,7 @@ func (t uploadSpecTask) Run(ctx context.Context) error {
 		return err
 	}
 
-	compressed, err := core.GZippedBytes(contents)
+	compressed, err := compress.GZippedBytes(contents)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/registry/cmd/upload/discovery.go b/cmd/registry/cmd/upload/discovery.go
index 9c2df13..f2cba0f 100644
--- a/cmd/registry/cmd/upload/discovery.go
+++ b/cmd/registry/cmd/upload/discovery.go
@@ -19,6 +19,7 @@ import (
 	"encoding/json"
 	"fmt"
 
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/core"
 	"github.com/apigee/registry/cmd/registry/types"
 	"github.com/apigee/registry/log"
@@ -187,7 +188,7 @@ func (task *uploadDiscoveryTask) createOrUpdateSpec(ctx context.Context) error {
 		return nil
 	}
 
-	gzippedContents, err := core.GZippedBytes(task.contents)
+	gzippedContents, err := compress.GZippedBytes(task.contents)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/registry/cmd/upload/openapi.go b/cmd/registry/cmd/upload/openapi.go
index 506439e..48ae695 100644
--- a/cmd/registry/cmd/upload/openapi.go
+++ b/cmd/registry/cmd/upload/openapi.go
@@ -23,6 +23,7 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/core"
 	"github.com/apigee/registry/cmd/registry/types"
 	"github.com/apigee/registry/log"
@@ -237,7 +238,7 @@ func (task *uploadOpenAPITask) createOrUpdateSpec(ctx context.Context) error {
 		return nil
 	}
 
-	gzippedContents, err := core.GZippedBytes(task.contents)
+	gzippedContents, err := compress.GZippedBytes(task.contents)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/registry/cmd/upload/protos.go b/cmd/registry/cmd/upload/protos.go
index 7dd2fa9..c77acdd 100644
--- a/cmd/registry/cmd/upload/protos.go
+++ b/cmd/registry/cmd/upload/protos.go
@@ -26,6 +26,7 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/core"
 	"github.com/apigee/registry/cmd/registry/types"
 	"github.com/apigee/registry/log"
@@ -325,7 +326,7 @@ func (task *uploadProtoTask) zipContents() ([]byte, error) {
 	}
 
 	// Zip the listed files.
-	contents, err := core.ZipArchiveOfFiles(append(metadata, protos...), prefix, true)
+	contents, err := compress.ZipArchiveOfFiles(append(metadata, protos...), prefix)
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/registry/cmd/vocabulary/vocabulary.go b/cmd/registry/cmd/vocabulary/vocabulary.go
index 18a82ce..d974b3b 100644
--- a/cmd/registry/cmd/vocabulary/vocabulary.go
+++ b/cmd/registry/cmd/vocabulary/vocabulary.go
@@ -18,6 +18,7 @@ import (
 	"context"
 	"strings"
 
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/core"
 	"github.com/apigee/registry/cmd/registry/types"
 	"github.com/apigee/registry/log"
@@ -93,7 +94,7 @@ func setVocabularyToArtifact(ctx context.Context, client connection.RegistryClie
 	relation := parts[1]
 	messageData, _ := proto.Marshal(output)
 	var err error
-	messageData, err = core.GZippedBytes(messageData)
+	messageData, err = compress.GZippedBytes(messageData)
 	if err != nil {
 		log.FromContext(ctx).WithError(err).Fatal("Failed to compress artifact")
 	}
diff --git a/cmd/registry/core/gzip.go b/cmd/registry/compress/gzip.go
similarity index 98%
rename from cmd/registry/core/gzip.go
rename to cmd/registry/compress/gzip.go
index 1b62f65..31e0586 100644
--- a/cmd/registry/core/gzip.go
+++ b/cmd/registry/compress/gzip.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package core
+package compress
 
 import (
 	"bytes"
diff --git a/cmd/registry/core/zip.go b/cmd/registry/compress/zip.go
similarity index 97%
rename from cmd/registry/core/zip.go
rename to cmd/registry/compress/zip.go
index bbf8e2d..0437ab8 100644
--- a/cmd/registry/core/zip.go
+++ b/cmd/registry/compress/zip.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package core
+package compress
 
 import (
 	"archive/zip"
@@ -124,7 +124,7 @@ func ZipArchiveOfPath(path, prefix string, recursive bool) (buf bytes.Buffer, er
 
 // ZipArchiveOfFiles stores a list of files in a zip archive.
 // The specified prefix is stripped from file names in the archive.
-func ZipArchiveOfFiles(files []string, prefix string, recursive bool) (buf bytes.Buffer, err error) {
+func ZipArchiveOfFiles(files []string, prefix string) (buf bytes.Buffer, err error) {
 	zipWriter := zip.NewWriter(&buf)
 	defer zipWriter.Close()
 
diff --git a/cmd/registry/conformance/conformance-task.go b/cmd/registry/conformance/conformance-task.go
index b8bf911..e112b22 100644
--- a/cmd/registry/conformance/conformance-task.go
+++ b/cmd/registry/conformance/conformance-task.go
@@ -23,6 +23,7 @@ import (
 	"path/filepath"
 	"time"
 
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/core"
 	"github.com/apigee/registry/cmd/registry/types"
 	"github.com/apigee/registry/log"
@@ -105,7 +106,7 @@ func (task *ComputeConformanceTask) Run(ctx context.Context) error {
 	defer os.RemoveAll(root)
 
 	if types.IsZipArchive(task.Spec.GetMimeType()) {
-		_, err = core.UnzipArchiveToPath(data, root)
+		_, err = compress.UnzipArchiveToPath(data, root)
 	} else {
 		// Write the file to the temporary directory.
 		err = os.WriteFile(filepath.Join(root, name), data, 0644)
diff --git a/cmd/registry/patch/project.go b/cmd/registry/patch/project.go
index f7cde49..5f156da 100644
--- a/cmd/registry/patch/project.go
+++ b/cmd/registry/patch/project.go
@@ -22,6 +22,7 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/core"
 	"github.com/apigee/registry/gapic"
 	"github.com/apigee/registry/log"
@@ -497,7 +498,7 @@ func (task *exportSpecTask) Run(ctx context.Context) error {
 	}
 	data := contents.GetData()
 	if strings.Contains(contents.GetContentType(), "+gzip") {
-		data, _ = core.GUnzippedBytes(data)
+		data, _ = compress.GUnzippedBytes(data)
 	}
 	return os.WriteFile(filepath.Join(parentDir, task.message.Filename), data, 0644)
 }
diff --git a/cmd/registry/patch/spec.go b/cmd/registry/patch/spec.go
index ccf0374..f737c35 100644
--- a/cmd/registry/patch/spec.go
+++ b/cmd/registry/patch/spec.go
@@ -25,7 +25,7 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/apigee/registry/cmd/registry/core"
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/gapic"
 	"github.com/apigee/registry/pkg/connection"
 	"github.com/apigee/registry/pkg/models"
@@ -120,7 +120,7 @@ func applyApiSpecPatch(
 		body, err := os.ReadFile(filepath.Join(filepath.Dir(filename), spec.Data.FileName))
 		if err == nil {
 			if strings.Contains(spec.Data.MimeType, "+gzip") {
-				body, err = core.GZippedBytes(body)
+				body, err = compress.GZippedBytes(body)
 				if err != nil {
 					return err
 				}
@@ -145,7 +145,7 @@ func applyApiSpecPatch(
 				return err
 			}
 			if strings.Contains(spec.Data.MimeType, "+gzip") {
-				body, err = core.GZippedBytes(body)
+				body, err = compress.GZippedBytes(body)
 				if err != nil {
 					return err
 				}
@@ -169,7 +169,7 @@ func applyApiSpecPatch(
 						return err
 					}
 				}
-				contents, err := core.ZipArchiveOfPath(path, "", recursive)
+				contents, err := compress.ZipArchiveOfPath(path, "", recursive)
 				if err != nil {
 					return err
 				}
@@ -180,7 +180,7 @@ func applyApiSpecPatch(
 					return err
 				}
 				if strings.Contains(spec.Data.MimeType, "+gzip") {
-					body, err = core.GZippedBytes(body)
+					body, err = compress.GZippedBytes(body)
 					if err != nil {
 						return err
 					}


Test patch:
diff --git a/cmd/registry/cmd/get/get_test.go b/cmd/registry/cmd/get/get_test.go
index 3b67ed3..e97e219 100644
--- a/cmd/registry/cmd/get/get_test.go
+++ b/cmd/registry/cmd/get/get_test.go
@@ -19,7 +19,7 @@ import (
 	"context"
 	"testing"
 
-	"github.com/apigee/registry/cmd/registry/core"
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/cmd/registry/types"
 	"github.com/apigee/registry/pkg/connection"
 	"github.com/apigee/registry/pkg/connection/grpctest"
@@ -421,7 +421,7 @@ func TestGetValidResourcesWithFilter(t *testing.T) {
 
 func TestGetGZippedSpec(t *testing.T) {
 	payload := "hello"
-	contents, err := core.GZippedBytes([]byte(payload))
+	contents, err := compress.GZippedBytes([]byte(payload))
 	if err != nil {
 		t.Fatalf("Failed to create client: %+v", err)
 	}
diff --git a/cmd/registry/cmd/upload/protos_test.go b/cmd/registry/cmd/upload/protos_test.go
index 8f14537..bea7bc4 100644
--- a/cmd/registry/cmd/upload/protos_test.go
+++ b/cmd/registry/cmd/upload/protos_test.go
@@ -21,7 +21,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/apigee/registry/cmd/registry/core"
+	"github.com/apigee/registry/cmd/registry/compress"
 	"github.com/apigee/registry/pkg/connection"
 	"github.com/apigee/registry/rpc"
 	"google.golang.org/grpc/codes"
@@ -103,7 +103,7 @@ func TestProtos(t *testing.T) {
 			t.Errorf("Invalid mime type for %s: %s", test.spec, result.ContentType)
 		}
 		// Verify that the zip contains the expected number of files
-		m, err := core.UnzipArchiveToMap(result.Data)
+		m, err := compress.UnzipArchiveToMap(result.Data)
 		if err != nil {
 			t.Fatalf("unable to unzip spec %s", test.spec)
 		}
diff --git a/cmd/registry/compress/gzip_test.go b/cmd/registry/compress/gzip_test.go
new file mode 100644
index 0000000..b6ee42d
--- /dev/null
+++ b/cmd/registry/compress/gzip_test.go
@@ -0,0 +1,36 @@
+// Copyright 2023 Google LLC. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compress
+
+import "testing"
+
+func TestGZip(t *testing.T) {
+	initial := []byte("hello")
+	compressed, err := GZippedBytes(initial)
+	if err != nil {
+		t.Fatal("Failed to compress message")
+	}
+	final, err := GUnzippedBytes(compressed)
+	if err != nil {
+		t.Fatal("Failed to uncompress message")
+	}
+	if string(final) != string(initial) {
+		t.Error("Failed to preserve message through compression and uncompression")
+	}
+	_, err = GUnzippedBytes(initial)
+	if err == nil {
+		t.Error("Uncompression of uncompressed data succeeded and should have failed")
+	}
+}
diff --git a/cmd/registry/compress/testdata/sample/five b/cmd/registry/compress/testdata/sample/five
new file mode 100644
index 0000000..54f9d6d
--- /dev/null
+++ b/cmd/registry/compress/testdata/sample/five
@@ -0,0 +1,1 @@
+five
diff --git a/cmd/registry/compress/testdata/sample/one b/cmd/registry/compress/testdata/sample/one
new file mode 100644
index 0000000..5626abf
--- /dev/null
+++ b/cmd/registry/compress/testdata/sample/one
@@ -0,0 +1,1 @@
+one
diff --git a/cmd/registry/compress/testdata/sample/three/four/four b/cmd/registry/compress/testdata/sample/three/four/four
new file mode 100644
index 0000000..8510665
--- /dev/null
+++ b/cmd/registry/compress/testdata/sample/three/four/four
@@ -0,0 +1,1 @@
+four
diff --git a/cmd/registry/compress/testdata/sample/three/three b/cmd/registry/compress/testdata/sample/three/three
new file mode 100644
index 0000000..2bdf67a
--- /dev/null
+++ b/cmd/registry/compress/testdata/sample/three/three
@@ -0,0 +1,1 @@
+three
diff --git a/cmd/registry/compress/testdata/sample/two b/cmd/registry/compress/testdata/sample/two
new file mode 100644
index 0000000..f719efd
--- /dev/null
+++ b/cmd/registry/compress/testdata/sample/two
@@ -0,0 +1,1 @@
+two
diff --git a/cmd/registry/compress/zip_test.go b/cmd/registry/compress/zip_test.go
new file mode 100644
index 0000000..b6de5a2
--- /dev/null
+++ b/cmd/registry/compress/zip_test.go
@@ -0,0 +1,130 @@
+// Copyright 2023 Google LLC. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compress
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestZipArchiveOfPath(t *testing.T) {
+	tests := []struct {
+		name      string
+		recursive bool
+		expected  map[string][]byte
+	}{
+		{
+			"recursive",
+			true,
+			map[string][]byte{
+				"one":             []byte("one\n"),
+				"two":             []byte("two\n"),
+				"three/three":     []byte("three\n"),
+				"three/four/four": []byte("four\n"),
+				"five":            []byte("five\n"),
+			},
+		},
+		{
+			"nonrecursive",
+			false,
+			map[string][]byte{
+				"one":  []byte("one\n"),
+				"two":  []byte("two\n"),
+				"five": []byte("five\n"),
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			path := "testdata/sample"
+			b, err := ZipArchiveOfPath(path, "testdata/sample/", test.recursive)
+			if err != nil {
+				t.Fatalf("Failed to zip path %s", path)
+			}
+			a := b.Bytes()
+			m, err := UnzipArchiveToMap(a)
+			if err != nil {
+				t.Fatalf("Failed to unzip archive %s to map", path)
+			}
+			for k, v := range test.expected {
+				if !bytes.Equal(m[k], v) {
+					t.Errorf("failed to get file %s from archive", k)
+				}
+			}
+			for k, v := range m {
+				if !bytes.Equal(v, test.expected[k]) {
+					t.Errorf("found extra file %s in archive", k)
+				}
+			}
+			output := t.TempDir()
+			files, err := UnzipArchiveToPath(a, output)
+			if err != nil {
+				t.Fatalf("Failed to unzip archive %s to directory", path)
+			}
+			if len(files) != len(test.expected) {
+				t.Errorf("unzipped incorrect number of files %d, expected %d", len(files), len(test.expected))
+			}
+		})
+	}
+}
+
+func TestZipArchiveOfFiles(t *testing.T) {
+	tests := []struct {
+		name     string
+		files    []string
+		expected map[string][]byte
+	}{
+		{
+			"files-123",
+			[]string{"one", "two", "three/three"},
+			map[string][]byte{
+				"one":         []byte("one\n"),
+				"two":         []byte("two\n"),
+				"three/three": []byte("three\n"),
+			},
+		},
+		{
+			"files-45",
+			[]string{"three/four/four", "five"},
+			map[string][]byte{
+				"three/four/four": []byte("four\n"),
+				"five":            []byte("five\n"),
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			path := "testdata/sample"
+			b, err := ZipArchiveOfFiles(test.files, "testdata/sample/")
+			if err != nil {
+				t.Fatalf("Failed to zip path %s", path)
+			}
+			m, err := UnzipArchiveToMap(b.Bytes())
+			if err != nil {
+				t.Fatalf("Failed to unzip archive %s", path)
+			}
+			for k, v := range test.expected {
+				if !bytes.Equal(m[k], v) {
+					t.Errorf("failed to get file %s from archive", k)
+				}
+			}
+			for k, v := range m {
+				if !bytes.Equal(v, test.expected[k]) {
+					t.Errorf("found extra file %s in archive", k)
+				}
+			}
+		})
+	}
+}