// Copyright 2018 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 remote

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"path"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/logs"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/registry"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/mutate"
	"github.com/google/go-containerregistry/pkg/v1/partial"
	"github.com/google/go-containerregistry/pkg/v1/random"
	"github.com/google/go-containerregistry/pkg/v1/types"
	"github.com/google/go-containerregistry/pkg/v1/validate"
)

const bogusDigest = "sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"

type withDigest interface {
	Digest() (v1.Hash, error)
}

func mustDigest(t *testing.T, img withDigest) v1.Hash {
	h, err := img.Digest()
	if err != nil {
		t.Fatalf("Digest() = %v", err)
	}
	return h
}

func mustManifest(t *testing.T, img v1.Image) *v1.Manifest {
	m, err := img.Manifest()
	if err != nil {
		t.Fatalf("Manifest() = %v", err)
	}
	return m
}

func mustRawManifest(t *testing.T, img Taggable) []byte {
	m, err := img.RawManifest()
	if err != nil {
		t.Fatalf("RawManifest() = %v", err)
	}
	return m
}

func mustRawConfigFile(t *testing.T, img v1.Image) []byte {
	c, err := img.RawConfigFile()
	if err != nil {
		t.Fatalf("RawConfigFile() = %v", err)
	}
	return c
}

func randomImage(t *testing.T) v1.Image {
	rnd, err := random.Image(1024, 1)
	if err != nil {
		t.Fatalf("random.Image() = %v", err)
	}
	return rnd
}

func newReference(host, repo, ref string) (name.Reference, error) {
	tag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", host, repo, ref), name.WeakValidation)
	if err == nil {
		return tag, nil
	}
	return name.NewDigest(fmt.Sprintf("%s/%s@%s", host, repo, ref), name.WeakValidation)
}

// TODO(jonjohnsonjr): Make this real.
func TestMediaType(t *testing.T) {
	img := remoteImage{}
	got, err := img.MediaType()
	if err != nil {
		t.Fatalf("MediaType() = %v", err)
	}
	want := types.DockerManifestSchema2
	if got != want {
		t.Errorf("MediaType() = %v, want %v", got, want)
	}
}

func TestRawManifestDigests(t *testing.T) {
	img := randomImage(t)
	expectedRepo := "foo/bar"

	cases := []struct {
		name          string
		ref           string
		responseBody  []byte
		contentDigest string
		wantErr       bool
	}{{
		name:          "normal pull, by tag",
		ref:           "latest",
		responseBody:  mustRawManifest(t, img),
		contentDigest: mustDigest(t, img).String(),
		wantErr:       false,
	}, {
		name:          "normal pull, by digest",
		ref:           mustDigest(t, img).String(),
		responseBody:  mustRawManifest(t, img),
		contentDigest: mustDigest(t, img).String(),
		wantErr:       false,
	}, {
		name:          "right content-digest, wrong body, by digest",
		ref:           mustDigest(t, img).String(),
		responseBody:  []byte("not even json"),
		contentDigest: mustDigest(t, img).String(),
		wantErr:       true,
	}, {
		name:          "right body, wrong content-digest, by tag",
		ref:           "latest",
		responseBody:  mustRawManifest(t, img),
		contentDigest: bogusDigest,
		wantErr:       false,
	}, {
		// NB: This succeeds! We don't care what the registry thinks.
		name:          "right body, wrong content-digest, by digest",
		ref:           mustDigest(t, img).String(),
		responseBody:  mustRawManifest(t, img),
		contentDigest: bogusDigest,
		wantErr:       false,
	}}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			manifestPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, tc.ref)
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				switch r.URL.Path {
				case manifestPath:
					if r.Method != http.MethodGet {
						t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
					}

					w.Header().Set("Docker-Content-Digest", tc.contentDigest)
					w.Write(tc.responseBody)
				default:
					t.Fatalf("Unexpected path: %v", r.URL.Path)
				}
			}))
			defer server.Close()
			u, err := url.Parse(server.URL)
			if err != nil {
				t.Fatalf("url.Parse(%v) = %v", server.URL, err)
			}

			ref, err := newReference(u.Host, expectedRepo, tc.ref)
			if err != nil {
				t.Fatalf("url.Parse(%v, %v, %v) = %v", u.Host, expectedRepo, tc.ref, err)
			}

			rmt := remoteImage{
				ref: ref,
				ctx: context.Background(),
				fetcher: fetcher{
					target: ref.Context(),
					client: http.DefaultClient,
				},
			}

			if _, err := rmt.RawManifest(); (err != nil) != tc.wantErr {
				t.Errorf("RawManifest() wrong error: %v, want %v: %v\n", (err != nil), tc.wantErr, err)
			}
		})
	}
}

func TestRawManifestNotFound(t *testing.T) {
	expectedRepo := "foo/bar"
	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case manifestPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.WriteHeader(http.StatusNotFound)
		default:
			t.Fatalf("Unexpected path: %v", r.URL.Path)
		}
	}))
	defer server.Close()
	u, err := url.Parse(server.URL)
	if err != nil {
		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
	}

	ref := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
	img := remoteImage{
		ref: ref,
		ctx: context.Background(),
		fetcher: fetcher{
			target: ref.Context(),
			client: http.DefaultClient,
		},
	}

	if _, err := img.RawManifest(); err == nil {
		t.Error("RawManifest() = nil; wanted error")
	}
}

func TestRawConfigFileNotFound(t *testing.T) {
	img := randomImage(t)
	expectedRepo := "foo/bar"
	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, img))
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case configPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.WriteHeader(http.StatusNotFound)
		case manifestPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawManifest(t, img))
		default:
			t.Fatalf("Unexpected path: %v", r.URL.Path)
		}
	}))
	defer server.Close()
	u, err := url.Parse(server.URL)
	if err != nil {
		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
	}

	ref := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
	rmt := remoteImage{
		ref: ref,
		ctx: context.Background(),
		fetcher: fetcher{
			target: ref.Context(),
			client: http.DefaultClient,
		},
	}

	if _, err := rmt.RawConfigFile(); err == nil {
		t.Error("RawConfigFile() = nil; wanted error")
	}
}

func TestAcceptHeaders(t *testing.T) {
	img := randomImage(t)
	expectedRepo := "foo/bar"
	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case manifestPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			wantAccept := strings.Join([]string{
				string(types.DockerManifestSchema2),
				string(types.OCIManifestSchema1),
			}, ",")
			if got, want := r.Header.Get("Accept"), wantAccept; got != want {
				t.Errorf("Accept header; got %v, want %v", got, want)
			}
			w.Write(mustRawManifest(t, img))
		default:
			t.Fatalf("Unexpected path: %v", r.URL.Path)
		}
	}))
	defer server.Close()
	u, err := url.Parse(server.URL)
	if err != nil {
		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
	}
	ref := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
	rmt := &remoteImage{
		ref: ref,
		ctx: context.Background(),
		fetcher: fetcher{
			target: ref.Context(),
			client: http.DefaultClient,
		},
	}
	manifest, err := rmt.RawManifest()
	if err != nil {
		t.Errorf("RawManifest() = %v", err)
	}
	if got, want := manifest, mustRawManifest(t, img); !bytes.Equal(got, want) {
		t.Errorf("RawManifest() = %v, want %v", got, want)
	}
}

func TestImage(t *testing.T) {
	img := randomImage(t)
	expectedRepo := "foo/bar"
	layerDigest := mustManifest(t, img).Layers[0].Digest
	layerSize := mustManifest(t, img).Layers[0].Size
	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, img))
	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
	layerPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, layerDigest)
	manifestReqCount := 0

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/v2/":
			w.WriteHeader(http.StatusOK)
		case configPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawConfigFile(t, img))
		case manifestPath:
			manifestReqCount++
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawManifest(t, img))
		case layerPath:
			t.Fatalf("BlobSize should not make any request: %v", r.URL.Path)
		default:
			t.Fatalf("Unexpected path: %v", r.URL.Path)
		}
	}))
	defer server.Close()
	u, err := url.Parse(server.URL)
	if err != nil {
		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
	}

	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
	rmt, err := Image(tag, WithTransport(http.DefaultTransport), WithAuthFromKeychain(authn.DefaultKeychain))
	if err != nil {
		t.Errorf("Image() = %v", err)
	}

	if got, want := mustRawManifest(t, rmt), mustRawManifest(t, img); !bytes.Equal(got, want) {
		t.Errorf("RawManifest() = %v, want %v", got, want)
	}
	if got, want := mustRawConfigFile(t, rmt), mustRawConfigFile(t, img); !bytes.Equal(got, want) {
		t.Errorf("RawConfigFile() = %v, want %v", got, want)
	}
	// Make sure caching the manifest works.
	if manifestReqCount != 1 {
		t.Errorf("RawManifest made %v requests, expected 1", manifestReqCount)
	}

	l, err := rmt.LayerByDigest(layerDigest)
	if err != nil {
		t.Errorf("LayerByDigest() = %v", err)
	}
	// BlobSize should not HEAD.
	size, err := l.Size()
	if err != nil {
		t.Errorf("BlobSize() = %v", err)
	}
	if got, want := size, layerSize; want != got {
		t.Errorf("BlobSize() = %v want %v", got, want)
	}
}

func TestPullingManifestList(t *testing.T) {
	idx := randomIndex(t)
	expectedRepo := "foo/bar"
	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
	childDigest := mustIndexManifest(t, idx).Manifests[1].Digest
	child := mustChild(t, idx, childDigest)
	childPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, childDigest)
	fakePlatformChildDigest := mustIndexManifest(t, idx).Manifests[0].Digest
	fakePlatformChild := mustChild(t, idx, fakePlatformChildDigest)
	fakePlatformChildPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, fakePlatformChildDigest)
	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, child))

	fakePlatform := v1.Platform{
		Architecture: "not-real-arch",
		OS:           "not-real-os",
	}

	// Rewrite the index to make sure the desired platform matches the second child.
	manifest, err := idx.IndexManifest()
	if err != nil {
		t.Fatal(err)
	}
	// Make sure the first manifest doesn't match.
	manifest.Manifests[0].Platform = &fakePlatform
	// Make sure the second manifest does.
	manifest.Manifests[1].Platform = &defaultPlatform
	// Do short-circuiting via Data.
	manifest.Manifests[1].Data = mustRawManifest(t, child)
	rawManifest, err := json.Marshal(manifest)
	if err != nil {
		t.Fatal(err)
	}

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/v2/":
			w.WriteHeader(http.StatusOK)
		case manifestPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Header().Set("Content-Type", string(mustMediaType(t, idx)))
			w.Write(rawManifest)
		case childPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawManifest(t, child))
		case configPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawConfigFile(t, child))
		case fakePlatformChildPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawManifest(t, fakePlatformChild))
		default:
			t.Fatalf("Unexpected path: %v", r.URL.Path)
		}
	}))
	defer server.Close()
	u, err := url.Parse(server.URL)
	if err != nil {
		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
	}

	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
	rmtChild, err := Image(tag)
	if err != nil {
		t.Errorf("Image() = %v", err)
	}

	// Test that child works as expected.
	if got, want := mustRawManifest(t, rmtChild), mustRawManifest(t, child); !bytes.Equal(got, want) {
		t.Errorf("RawManifest() = %v, want %v", string(got), string(want))
	}
	if got, want := mustRawConfigFile(t, rmtChild), mustRawConfigFile(t, child); !bytes.Equal(got, want) {
		t.Errorf("RawConfigFile() = %v, want %v", got, want)
	}

	// Make sure we can roundtrip platform info via Descriptor.
	img, err := Image(tag, WithPlatform(fakePlatform))
	if err != nil {
		t.Fatal(err)
	}
	desc, err := partial.Descriptor(img)
	if err != nil {
		t.Fatal(err)
	}

	if diff := cmp.Diff(*desc.Platform, fakePlatform); diff != "" {
		t.Errorf("Desciptor() (-want +got) = %v", diff)
	}
}

func TestPullingManifestListNoMatch(t *testing.T) {
	idx := randomIndex(t)
	expectedRepo := "foo/bar"
	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
	childDigest := mustIndexManifest(t, idx).Manifests[1].Digest
	child := mustChild(t, idx, childDigest)
	childPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, childDigest)
	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, child))

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/v2/":
			w.WriteHeader(http.StatusOK)
		case manifestPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Header().Set("Content-Type", string(mustMediaType(t, idx)))
			w.Write(mustRawManifest(t, idx))
		case childPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawManifest(t, child))
		case configPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawConfigFile(t, child))
		default:
			t.Fatalf("Unexpected path: %v", r.URL.Path)
		}
	}))
	defer server.Close()
	u, err := url.Parse(server.URL)
	if err != nil {
		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
	}
	platform := v1.Platform{
		Architecture: "not-real-arch",
		OS:           "not-real-os",
	}
	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
	if _, err := Image(tag, WithPlatform(platform)); err == nil {
		t.Errorf("Image succeeded, wanted err")
	}
}

func TestValidate(t *testing.T) {
	img, err := random.Image(1024, 5)
	if err != nil {
		t.Fatal(err)
	}

	s := httptest.NewServer(registry.New())
	defer s.Close()
	u, err := url.Parse(s.URL)
	if err != nil {
		t.Fatal(err)
	}

	tag, err := name.NewTag(u.Host + "/foo/bar")
	if err != nil {
		t.Fatal(err)
	}

	if err := Write(tag, img); err != nil {
		t.Fatal(err)
	}

	img, err = Image(tag)
	if err != nil {
		t.Fatal(err)
	}

	if err := validate.Image(img); err != nil {
		t.Errorf("failed to validate remote.Image: %v", err)
	}
}

func TestPullingForeignLayer(t *testing.T) {
	// For that sweet, sweet coverage in options.
	var b bytes.Buffer
	logs.Debug.SetOutput(&b)

	img := randomImage(t)
	expectedRepo := "foo/bar"
	foreignPath := "/foreign/path"

	foreignLayer, err := random.Layer(1024, types.DockerForeignLayer)
	if err != nil {
		t.Fatal(err)
	}

	foreignServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case foreignPath:
			compressed, err := foreignLayer.Compressed()
			if err != nil {
				t.Fatal(err)
			}
			if _, err := io.Copy(w, compressed); err != nil {
				t.Fatal(err)
			}
			w.WriteHeader(http.StatusOK)
		default:
			t.Fatalf("Unexpected path: %v", r.URL.Path)
		}
	}))
	defer foreignServer.Close()
	fu, err := url.Parse(foreignServer.URL)
	if err != nil {
		t.Fatalf("url.Parse(%v) = %v", foreignServer.URL, err)
	}

	img, err = mutate.Append(img, mutate.Addendum{
		Layer: foreignLayer,
		URLs: []string{
			"http://" + path.Join(fu.Host, foreignPath),
		},
	})
	if err != nil {
		t.Fatal(err)
	}

	// Set up a fake registry that will respond 404 to the foreign layer,
	// but serve everything else correctly.
	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, img))
	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
	foreignLayerDigest := mustManifest(t, img).Layers[1].Digest
	foreignLayerPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, foreignLayerDigest)
	layerDigest := mustManifest(t, img).Layers[0].Digest
	layerPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, layerDigest)

	layer, err := img.LayerByDigest(layerDigest)
	if err != nil {
		t.Fatal(err)
	}

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/v2/":
			w.WriteHeader(http.StatusOK)
		case configPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawConfigFile(t, img))
		case manifestPath:
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(mustRawManifest(t, img))
		case layerPath:
			compressed, err := layer.Compressed()
			if err != nil {
				t.Fatal(err)
			}
			if _, err := io.Copy(w, compressed); err != nil {
				t.Fatal(err)
			}
			w.WriteHeader(http.StatusOK)
		case foreignLayerPath:
			// Not here!
			w.WriteHeader(http.StatusNotFound)
		default:
			t.Fatalf("Unexpected path: %v", r.URL.Path)
		}
	}))
	defer server.Close()
	u, err := url.Parse(server.URL)
	if err != nil {
		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
	}

	// Pull from the registry and ensure that everything Validates; i.e. that
	// we pull the layer from the foreignServer.
	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
	rmt, err := Image(tag, WithTransport(http.DefaultTransport))
	if err != nil {
		t.Errorf("Image() = %v", err)
	}

	if err := validate.Image(rmt); err != nil {
		t.Errorf("failed to validate foreign image: %v", err)
	}

	// Set up a fake registry and write what we pulled to it.
	// This ensures we get coverage for the remoteLayer.MediaType path.
	s := httptest.NewServer(registry.New())
	defer s.Close()
	u, err = url.Parse(s.URL)
	if err != nil {
		t.Fatal(err)
	}
	dst := fmt.Sprintf("%s/test/foreign/upload", u.Host)
	ref, err := name.ParseReference(dst)
	if err != nil {
		t.Fatal(err)
	}

	if err := Write(ref, rmt); err != nil {
		t.Errorf("failed to Write: %v", err)
	}
}

func TestData(t *testing.T) {
	img := randomImage(t)
	manifest, err := img.Manifest()
	if err != nil {
		t.Fatal(err)
	}
	layers, err := img.Layers()
	if err != nil {
		t.Fatal(err)
	}
	cb, err := img.RawConfigFile()
	if err != nil {
		t.Fatal(err)
	}

	manifest.Config.Data = cb
	rc, err := layers[0].Compressed()
	if err != nil {
		t.Fatal(err)
	}
	lb, err := io.ReadAll(rc)
	if err != nil {
		t.Fatal(err)
	}
	manifest.Layers[0].Data = lb
	rawManifest, err := json.Marshal(manifest)
	if err != nil {
		t.Fatal(err)
	}

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/v2/":
			w.WriteHeader(http.StatusOK)
		case "/v2/test/manifests/latest":
			if r.Method != http.MethodGet {
				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
			}
			w.Write(rawManifest)
		default:
			// explode if we try to read blob or config
			t.Fatalf("Unexpected path: %v", r.URL.Path)
		}
	}))
	defer server.Close()
	u, err := url.Parse(server.URL)
	if err != nil {
		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
	}
	ref, err := newReference(u.Host, "test", "latest")
	if err != nil {
		t.Fatal(err)
	}
	rmt, err := Image(ref)
	if err != nil {
		t.Fatal(err)
	}
	if err := validate.Image(rmt); err != nil {
		t.Fatal(err)
	}
}
