Skip to content

Latest commit

 

History

History
206 lines (176 loc) · 5.27 KB

FakeGoogleCloudStorage.md

File metadata and controls

206 lines (176 loc) · 5.27 KB

The following is an example of using dockertest & fake-gcs-server to perform integration testing with the Google Cloud Storage API and a local Google Cloud Storage emulator server.

This example assumes there is a local bind mount at {$PWD}/examples/data with the sample-bucket/sample_file.txt file.

You also need to substitute path/to/your/credentials.json with your correct Google API credentials file to be able to use the GCS API.

Make sure to checkout the original fake-gcs-server project at https://github.com/fsouza/fake-gcs-server for more details about its configuration and functionality.

package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"testing"

	"cloud.google.com/go/storage"
	"github.com/ory/dockertest/v3"
	"github.com/ory/dockertest/v3/docker"
	"google.golang.org/api/iterator"
	"google.golang.org/api/option"
)

type HostFixRoundTripper struct {
	Proxy http.RoundTripper
}

func (l HostFixRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
	request.Host = "gcs:4443"
	res, err := l.Proxy.RoundTrip(request)
	if res != nil {
		location := res.Header.Get("Location")
		if len(location) != 0 {
			res.Header.Set("Location", strings.Replace(location, "gcs", "localhost", 1))
		}
	}
	return res, err
}

var pool *dockertest.Pool

func TestMain(m *testing.M) {
	setUpGcloud()
	var err error
	pool, err = dockertest.NewPool("")
	if err != nil {
		log.Fatalf("Could not construct pool: %s", err)
		os.Exit(1)
	}

	err = pool.Client.Ping()
	if err != nil {
		log.Fatalf("Could not connect to Docker: %s", err)
	}

	m.Run()
}

func setUpGcloud() {
	os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "path/to/your/credentials.json")
}

func TestFakeGCloudStorage(t *testing.T) {
	pwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("failed to get working directory: %s", err)
	}

	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
		Repository:   "fsouza/fake-gcs-server",
		Tag:          "latest",
		Name:         "fake-gcs-server",
		ExposedPorts: []string{"4443"},
		Cmd:          []string{"-backend", "memory", "-scheme", "http", "-port", "4443", "-public-host", "gcs:4443", "-external-url", "http://gcs:4443"},
	}, func(config *docker.HostConfig) {
		// set AutoRemove to true so that stopped container goes away by itself
		config.AutoRemove = true
		config.RestartPolicy = docker.RestartPolicy{
			Name: "no",
		}
		config.Mounts = []docker.HostMount{
			{
				Target: "/data",
				Source: fmt.Sprintf("%s/examples/data", pwd),
				Type:   "bind",
			},
		}
	})
	if err != nil {
		t.Fatalf("Could not start resource: %s", err)
	}

	endpoint := fmt.Sprintf("http://localhost:%s/storage/v1/", resource.GetPort("4443/tcp"))
	t.Logf("client endpoint: %+v", endpoint)
	client, err := storage.NewClient(
		context.TODO(),
		option.WithEndpoint(endpoint),
		option.WithoutAuthentication(),
		option.WithHTTPClient(&http.Client{
			Transport: &HostFixRoundTripper{&http.Transport{}},
		}),
	)
	if err != nil {
		t.Fatalf("Could not connect to docker - failed to create client: %v", err)
	}

	const (
		bucketName  = "sample-bucket"
		fileName    = "some_file.txt"
		newFileName = "new_file.txt"
	)

	buckets, err := list(client, bucketName)
	if err != nil {
		t.Fatalf("failed to list: %v", err)
	}
	t.Logf("buckets: %+v\n", buckets)

	data, err := readFile(client, bucketName, fileName)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("contents of %s/%s: %s\n", bucketName, fileName, data)

	err = createFile(client, bucketName, newFileName)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("new file '%+v' created\n", newFileName)

	err = deleteFile(client, bucketName, newFileName)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("file %s deleted\n", newFileName)

	t.Cleanup(func() {
		err := pool.Purge(resource)
		if err != nil {
			t.Logf("Could not purge resource: %s", err)
		}
	})
}

func list(client *storage.Client, bucketName string) ([]string, error) {
	var objects []string
	it := client.Bucket(bucketName).Objects(context.Background(), &storage.Query{})
	for {
		oattrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return nil, err
		}
		objects = append(objects, oattrs.Name)
	}
	return objects, nil
}

func createFile(client *storage.Client, bucketName, fileName string) error {
	wc := client.Bucket(bucketName).Object(fileName).NewWriter(context.TODO())
	wc.ContentType = "text/plain"
	wc.Metadata = map[string]string{
		"x-goog-meta-foo": "foo",
		"x-goog-meta-bar": "bar",
	}

	if _, err := wc.Write([]byte("abcde\n")); err != nil {
		return fmt.Errorf("unable to write data to bucket %q, file %q: %v", bucketName, fileName, err)
	}

	if _, err := wc.Write([]byte(strings.Repeat("f", 1024*4) + "\n")); err != nil {
		return fmt.Errorf("unable to write data to bucket %q, file %q: %v", bucketName, fileName, err)
	}

	if err := wc.Close(); err != nil {
		return fmt.Errorf("unable to close bucket %q, file %q: %v", bucketName, fileName, err)
	}

	return nil
}

func readFile(client *storage.Client, bucketName, fileName string) ([]byte, error) {
	reader, err := client.Bucket(bucketName).Object(fileName).NewReader(context.TODO())
	if err != nil {
		return nil, err
	}
	defer reader.Close()
	return io.ReadAll(reader)
}

func deleteFile(client *storage.Client, bucketName, fileName string) error {
	return client.Bucket(bucketName).Object(fileName).Delete(context.TODO())
}