/*
Copyright 2014 The Kubernetes Authors.
Copyright 2020 Authors of Arktos - file modified.

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 endpoints

import (
	"bytes"
	"compress/gzip"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"

	restful "github.com/emicklei/go-restful"
	fuzzer "k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
	apiequality "k8s.io/apimachinery/pkg/api/equality"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/diff"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/apiserver/pkg/admission"
	auditinternal "k8s.io/apiserver/pkg/apis/audit"
	"k8s.io/apiserver/pkg/apis/example"
	examplefuzzer "k8s.io/apiserver/pkg/apis/example/fuzzer"
	examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
	"k8s.io/apiserver/pkg/audit"
	auditpolicy "k8s.io/apiserver/pkg/audit/policy"
	genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
	"k8s.io/apiserver/pkg/endpoints/request"
	genericapitesting "k8s.io/apiserver/pkg/endpoints/testing"
	"k8s.io/apiserver/pkg/features"
	"k8s.io/apiserver/pkg/registry/rest"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	featuregatetesting "k8s.io/component-base/featuregate/testing"
)

type alwaysMutatingDeny struct{}

func (alwaysMutatingDeny) Admit(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
	return admission.NewForbidden(a, errors.New("Mutating admission control is denying all modifications"))
}

func (alwaysMutatingDeny) Handles(operation admission.Operation) bool {
	return true
}

type alwaysValidatingDeny struct{}

func (alwaysValidatingDeny) Validate(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
	return admission.NewForbidden(a, errors.New("Validating admission control is denying all modifications"))
}

func (alwaysValidatingDeny) Handles(operation admission.Operation) bool {
	return true
}

// This creates fake API versions, similar to api/latest.go.
var testAPIGroup = "test.group"
var testAPIGroup2 = "test.group2"
var testInternalGroupVersion = schema.GroupVersion{Group: testAPIGroup, Version: runtime.APIVersionInternal}
var testGroupVersion = schema.GroupVersion{Group: testAPIGroup, Version: "version"}
var newGroupVersion = schema.GroupVersion{Group: testAPIGroup, Version: "version2"}
var testGroup2Version = schema.GroupVersion{Group: testAPIGroup2, Version: "version"}
var testInternalGroup2Version = schema.GroupVersion{Group: testAPIGroup2, Version: runtime.APIVersionInternal}
var prefix = "apis"

var grouplessGroupVersion = schema.GroupVersion{Group: "", Version: "v1"}
var grouplessInternalGroupVersion = schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}
var grouplessPrefix = "api"

var groupVersions = []schema.GroupVersion{grouplessGroupVersion, testGroupVersion, newGroupVersion}

var scheme = runtime.NewScheme()
var codecs = serializer.NewCodecFactory(scheme)

var codec = codecs.LegacyCodec(groupVersions...)
var testCodec = codecs.LegacyCodec(testGroupVersion)
var newCodec = codecs.LegacyCodec(newGroupVersion)
var parameterCodec = runtime.NewParameterCodec(scheme)

var accessor = meta.NewAccessor()
var selfLinker runtime.SelfLinker = accessor
var admissionControl admission.Interface

func init() {
	metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion)

	// unnamed core group
	scheme.AddUnversionedTypes(grouplessGroupVersion, &metav1.Status{})
	metav1.AddToGroupVersion(scheme, grouplessGroupVersion)

	utilruntime.Must(example.AddToScheme(scheme))
	utilruntime.Must(examplev1.AddToScheme(scheme))
}

func addGrouplessTypes() {
	scheme.AddKnownTypes(grouplessGroupVersion,
		&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ListOptions{}, &metav1.ExportOptions{},
		&metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{})
	scheme.AddKnownTypes(grouplessInternalGroupVersion,
		&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{},
		&genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{})
}

func addTestTypes() {
	scheme.AddKnownTypes(testGroupVersion,
		&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{},
		&metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{},
		&genericapitesting.SimpleXGSubresource{})
	scheme.AddKnownTypes(testGroupVersion, &examplev1.Pod{})
	scheme.AddKnownTypes(testInternalGroupVersion,
		&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{},
		&genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{},
		&genericapitesting.SimpleXGSubresource{})
	scheme.AddKnownTypes(testInternalGroupVersion, &example.Pod{})
	// Register SimpleXGSubresource in both testGroupVersion and testGroup2Version, and also their
	// their corresponding internal versions, to verify that the desired group version object is
	// served in the tests.
	scheme.AddKnownTypes(testGroup2Version, &genericapitesting.SimpleXGSubresource{}, &metav1.ExportOptions{})
	scheme.AddKnownTypes(testInternalGroup2Version, &genericapitesting.SimpleXGSubresource{}, &metav1.ExportOptions{})
	metav1.AddToGroupVersion(scheme, testGroupVersion)
}

func addNewTestTypes() {
	scheme.AddKnownTypes(newGroupVersion,
		&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{},
		&metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{},
		&examplev1.Pod{},
	)
	metav1.AddToGroupVersion(scheme, newGroupVersion)
}

func init() {
	// Certain API objects are returned regardless of the contents of storage:
	// api.Status is returned in errors

	addGrouplessTypes()
	addTestTypes()
	addNewTestTypes()

	scheme.AddFieldLabelConversionFunc(grouplessGroupVersion.WithKind("Simple"),
		func(label, value string) (string, string, error) {
			return label, value, nil
		},
	)
	scheme.AddFieldLabelConversionFunc(testGroupVersion.WithKind("Simple"),
		func(label, value string) (string, string, error) {
			return label, value, nil
		},
	)
	scheme.AddFieldLabelConversionFunc(newGroupVersion.WithKind("Simple"),
		func(label, value string) (string, string, error) {
			return label, value, nil
		},
	)
}

// defaultAPIServer exposes nested objects for testability.
type defaultAPIServer struct {
	http.Handler
	container *restful.Container
}

// uses the default settings
func handle(storage map[string]rest.Storage) http.Handler {
	return handleInternal(storage, admissionControl, selfLinker, nil)
}

// tests using a custom self linker
func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker) http.Handler {
	return handleInternal(storage, admissionControl, selfLinker, nil)
}

func handleInternal(storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker, auditSink audit.Sink) http.Handler {
	container := restful.NewContainer()
	container.Router(restful.CurlyRouter{})
	mux := container.ServeMux

	template := APIGroupVersion{
		Storage: storage,

		Creater:         scheme,
		Convertor:       scheme,
		UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme),
		Defaulter:       scheme,
		Typer:           scheme,
		Linker:          selfLinker,
		RootScopedKinds: sets.NewString("SimpleRoot"),

		EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(),

		ParameterCodec: parameterCodec,

		Admit: admissionControl,
	}

	// groupless v1 version
	{
		group := template
		group.Root = "/" + grouplessPrefix
		group.GroupVersion = grouplessGroupVersion
		group.OptionsExternalVersion = &grouplessGroupVersion
		group.Serializer = codecs
		if err := (&group).InstallREST(container); err != nil {
			panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err))
		}
	}

	// group version 1
	{
		group := template
		group.Root = "/" + prefix
		group.GroupVersion = testGroupVersion
		group.OptionsExternalVersion = &testGroupVersion
		group.Serializer = codecs
		if err := (&group).InstallREST(container); err != nil {
			panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err))
		}
	}

	// group version 2
	{
		group := template
		group.Root = "/" + prefix
		group.GroupVersion = newGroupVersion
		group.OptionsExternalVersion = &newGroupVersion
		group.Serializer = codecs
		if err := (&group).InstallREST(container); err != nil {
			panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err))
		}
	}
	handler := genericapifilters.WithAudit(mux, auditSink, auditpolicy.FakeChecker(auditinternal.LevelRequestResponse, nil), func(r *http.Request, requestInfo *request.RequestInfo) bool {
		// simplified long-running check
		return requestInfo.Verb == "watch" || requestInfo.Verb == "proxy"
	})
	handler = genericapifilters.WithRequestInfo(handler, testRequestInfoResolver())

	return &defaultAPIServer{handler, container}
}

func testRequestInfoResolver() *request.RequestInfoFactory {
	return &request.RequestInfoFactory{
		APIPrefixes:          sets.NewString("api", "apis"),
		GrouplessAPIPrefixes: sets.NewString("api"),
	}
}

func TestSimpleSetupRight(t *testing.T) {
	s := &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "aName"}}
	wire, err := runtime.Encode(codec, s)
	if err != nil {
		t.Fatal(err)
	}
	s2, err := runtime.Decode(codec, wire)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(s, s2) {
		t.Fatalf("encode/decode broken:\n%#v\n%#v\n", s, s2)
	}
}

func TestSimpleOptionsSetupRight(t *testing.T) {
	s := &genericapitesting.SimpleGetOptions{}
	wire, err := runtime.Encode(codec, s)
	if err != nil {
		t.Fatal(err)
	}
	s2, err := runtime.Decode(codec, wire)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(s, s2) {
		t.Fatalf("encode/decode broken:\n%#v\n%#v\n", s, s2)
	}
}

type SimpleRESTStorage struct {
	lock sync.Mutex

	errors map[string]error
	list   []genericapitesting.Simple
	item   genericapitesting.Simple

	updated *genericapitesting.Simple
	created *genericapitesting.Simple

	stream *SimpleStream

	deleted       string
	deleteOptions *metav1.DeleteOptions

	actualNamespace  string
	namespacePresent bool

	// These are set when Watch is called
	fakeWatch                  *watch.FakeWatcher
	requestedLabelSelector     labels.Selector
	requestedFieldSelector     fields.Selector
	requestedResourceVersion   string
	requestedResourceNamespace string

	// The id requested, and location to return for ResourceLocation
	requestedResourceLocationID string
	resourceLocation            *url.URL
	resourceLocationTransport   http.RoundTripper
	expectedResourceNamespace   string

	actualTenant            string
	tenantPresent           bool
	requestedResourceTenant string
	expectedResourceTenant  string

	// If non-nil, called inside the WorkFunc when answering update, delete, create.
	// obj receives the original input to the update, delete, or create call.
	injectedFunction func(obj runtime.Object) (returnObj runtime.Object, err error)
}

func (storage *SimpleRESTStorage) NamespaceScoped() bool {
	return true
}

func (storage *SimpleRESTStorage) TenantScoped() bool {
	return true
}

func (storage *SimpleRESTStorage) Export(ctx context.Context, name string, opts metav1.ExportOptions) (runtime.Object, error) {
	obj, err := storage.Get(ctx, name, &metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	s, ok := obj.(*genericapitesting.Simple)
	if !ok {
		return nil, fmt.Errorf("unexpected object")
	}

	// Set a marker to verify the method was called
	s.Other = "exported"
	return obj, storage.errors["export"]
}

func (storage *SimpleRESTStorage) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
	return rest.NewDefaultTableConvertor(schema.GroupResource{Resource: "simple"}).ConvertToTable(ctx, obj, tableOptions)
}

func (storage *SimpleRESTStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
	storage.checkContext(ctx)
	result := &genericapitesting.SimpleList{
		ListMeta: metav1.ListMeta{
			ResourceVersion: "10",
			SelfLink:        "/test/link",
		},
		Items: storage.list,
	}
	storage.requestedLabelSelector = labels.Everything()
	if options != nil && options.LabelSelector != nil {
		storage.requestedLabelSelector = options.LabelSelector
	}
	storage.requestedFieldSelector = fields.Everything()
	if options != nil && options.FieldSelector != nil {
		storage.requestedFieldSelector = options.FieldSelector
	}
	return result, storage.errors["list"]
}

type SimpleTenantRESTStorage struct {
	SimpleRESTStorage
}

func (storage *SimpleTenantRESTStorage) NamespaceScoped() bool {
	return false
}

func (storage *SimpleTenantRESTStorage) TenantScoped() bool {
	return true
}

type SimpleRootRESTStorage struct {
	SimpleRESTStorage
}

func (storage *SimpleRootRESTStorage) NamespaceScoped() bool {
	return false
}

func (storage *SimpleRootRESTStorage) TenantScoped() bool {
	return false
}

type SimpleStream struct {
	version     string
	accept      string
	contentType string
	err         error

	io.Reader
	closed bool
}

func (s *SimpleStream) Close() error {
	s.closed = true
	return nil
}

func (obj *SimpleStream) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind }
func (obj *SimpleStream) DeepCopyObject() runtime.Object {
	panic("SimpleStream does not support DeepCopy")
}

func (s *SimpleStream) InputStream(_ context.Context, version, accept string) (io.ReadCloser, bool, string, error) {
	s.version = version
	s.accept = accept
	return s, false, s.contentType, s.err
}

type OutputConnect struct {
	response string
}

func (h *OutputConnect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte(h.response))
}

func (storage *SimpleRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) {
	storage.checkContext(ctx)
	if id == "binary" {
		return storage.stream, storage.errors["get"]
	}
	return storage.item.DeepCopy(), storage.errors["get"]
}

func (storage *SimpleRESTStorage) checkContext(ctx context.Context) {
	storage.actualNamespace, storage.namespacePresent = request.NamespaceFrom(ctx)
	storage.actualTenant, storage.tenantPresent = request.TenantFrom(ctx)
}

func (storage *SimpleRESTStorage) Delete(ctx context.Context, id string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
	storage.checkContext(ctx)
	storage.deleted = id
	storage.deleteOptions = options
	if err := storage.errors["delete"]; err != nil {
		return nil, false, err
	}
	if err := deleteValidation(&storage.item); err != nil {
		return nil, false, err
	}
	var obj runtime.Object = &metav1.Status{Status: metav1.StatusSuccess}
	var err error
	if storage.injectedFunction != nil {
		obj, err = storage.injectedFunction(&genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: id}})
	}
	return obj, true, err
}

func (storage *SimpleRESTStorage) New() runtime.Object {
	return &genericapitesting.Simple{}
}

func (storage *SimpleRESTStorage) NewList() runtime.Object {
	return &genericapitesting.SimpleList{}
}

func (storage *SimpleRESTStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	storage.checkContext(ctx)
	storage.created = obj.(*genericapitesting.Simple)
	if err := storage.errors["create"]; err != nil {
		return nil, err
	}
	var err error
	if storage.injectedFunction != nil {
		obj, err = storage.injectedFunction(obj)
	}
	if err := createValidation(obj); err != nil {
		return nil, err
	}
	return obj, err
}

func (storage *SimpleRESTStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	storage.checkContext(ctx)
	obj, err := objInfo.UpdatedObject(ctx, &storage.item)
	if err != nil {
		return nil, false, err
	}
	storage.updated = obj.(*genericapitesting.Simple)
	if err := storage.errors["update"]; err != nil {
		return nil, false, err
	}
	if storage.injectedFunction != nil {
		obj, err = storage.injectedFunction(obj)
	}
	if err := updateValidation(&storage.item, obj); err != nil {
		return nil, false, err
	}
	return obj, false, err
}

// Implement ResourceWatcher.
func (storage *SimpleRESTStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
	storage.lock.Lock()
	defer storage.lock.Unlock()
	storage.checkContext(ctx)
	storage.requestedLabelSelector = labels.Everything()
	if options != nil && options.LabelSelector != nil {
		storage.requestedLabelSelector = options.LabelSelector
	}
	storage.requestedFieldSelector = fields.Everything()
	if options != nil && options.FieldSelector != nil {
		storage.requestedFieldSelector = options.FieldSelector
	}
	storage.requestedResourceVersion = ""
	if options != nil {
		storage.requestedResourceVersion = options.ResourceVersion
	}
	storage.requestedResourceNamespace = request.NamespaceValue(ctx)
	storage.requestedResourceTenant = request.TenantValue(ctx)
	if err := storage.errors["watch"]; err != nil {
		return nil, err
	}
	storage.fakeWatch = watch.NewFake()
	return storage.fakeWatch, nil
}

func (storage *SimpleRESTStorage) Watcher() *watch.FakeWatcher {
	storage.lock.Lock()
	defer storage.lock.Unlock()
	return storage.fakeWatch
}

// Implement Connecter
type ConnecterRESTStorage struct {
	connectHandler http.Handler
	handlerFunc    func() http.Handler

	emptyConnectOptions    runtime.Object
	receivedConnectOptions runtime.Object
	receivedID             string
	receivedResponder      rest.Responder
	takesPath              string
}

// Implement Connecter
var _ = rest.Connecter(&ConnecterRESTStorage{})

func (s *ConnecterRESTStorage) New() runtime.Object {
	return &genericapitesting.Simple{}
}

func (s *ConnecterRESTStorage) Connect(ctx context.Context, id string, options runtime.Object, responder rest.Responder) (http.Handler, error) {
	s.receivedConnectOptions = options
	s.receivedID = id
	s.receivedResponder = responder
	if s.handlerFunc != nil {
		return s.handlerFunc(), nil
	}
	return s.connectHandler, nil
}

func (s *ConnecterRESTStorage) ConnectMethods() []string {
	return []string{"GET", "POST", "PUT", "DELETE"}
}

func (s *ConnecterRESTStorage) NewConnectOptions() (runtime.Object, bool, string) {
	if len(s.takesPath) > 0 {
		return s.emptyConnectOptions, true, s.takesPath
	}
	return s.emptyConnectOptions, false, ""
}

type MetadataRESTStorage struct {
	*SimpleRESTStorage
	types []string
}

func (m *MetadataRESTStorage) ProducesMIMETypes(method string) []string {
	return m.types
}

func (m *MetadataRESTStorage) ProducesObject(verb string) interface{} {
	return nil
}

var _ rest.StorageMetadata = &MetadataRESTStorage{}

type GetWithOptionsRESTStorage struct {
	*SimpleRESTStorage
	optionsReceived runtime.Object
	takesPath       string
}

func (r *GetWithOptionsRESTStorage) Get(ctx context.Context, name string, options runtime.Object) (runtime.Object, error) {
	if _, ok := options.(*genericapitesting.SimpleGetOptions); !ok {
		return nil, fmt.Errorf("Unexpected options object: %#v", options)
	}
	r.optionsReceived = options
	return r.SimpleRESTStorage.Get(ctx, name, &metav1.GetOptions{})
}

func (r *GetWithOptionsRESTStorage) NewGetOptions() (runtime.Object, bool, string) {
	if len(r.takesPath) > 0 {
		return &genericapitesting.SimpleGetOptions{}, true, r.takesPath
	}
	return &genericapitesting.SimpleGetOptions{}, false, ""
}

var _ rest.GetterWithOptions = &GetWithOptionsRESTStorage{}

type GetWithOptionsRootRESTStorage struct {
	*SimpleTypedStorage
	optionsReceived runtime.Object
	takesPath       string
}

func (r *GetWithOptionsRootRESTStorage) NamespaceScoped() bool {
	return false
}

func (r *GetWithOptionsRootRESTStorage) TenantScoped() bool {
	return false
}

func (r *GetWithOptionsRootRESTStorage) Get(ctx context.Context, name string, options runtime.Object) (runtime.Object, error) {
	if _, ok := options.(*genericapitesting.SimpleGetOptions); !ok {
		return nil, fmt.Errorf("Unexpected options object: %#v", options)
	}
	r.optionsReceived = options
	return r.SimpleTypedStorage.Get(ctx, name, &metav1.GetOptions{})
}

func (r *GetWithOptionsRootRESTStorage) NewGetOptions() (runtime.Object, bool, string) {
	if len(r.takesPath) > 0 {
		return &genericapitesting.SimpleGetOptions{}, true, r.takesPath
	}
	return &genericapitesting.SimpleGetOptions{}, false, ""
}

var _ rest.GetterWithOptions = &GetWithOptionsRootRESTStorage{}

type NamedCreaterRESTStorage struct {
	*SimpleRESTStorage
	createdName string
}

func (storage *NamedCreaterRESTStorage) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	storage.checkContext(ctx)
	storage.created = obj.(*genericapitesting.Simple)
	storage.createdName = name
	if err := storage.errors["create"]; err != nil {
		return nil, err
	}
	var err error
	if storage.injectedFunction != nil {
		obj, err = storage.injectedFunction(obj)
	}
	if err := createValidation(obj); err != nil {
		return nil, err
	}
	return obj, err
}

type SimpleTypedStorage struct {
	errors   map[string]error
	item     runtime.Object
	baseType runtime.Object

	actualNamespace  string
	namespacePresent bool

	actualTenant  string
	tenantPresent bool
}

func (storage *SimpleTypedStorage) New() runtime.Object {
	return storage.baseType
}

func (storage *SimpleTypedStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) {
	storage.checkContext(ctx)
	return storage.item.DeepCopyObject(), storage.errors["get"]
}

func (storage *SimpleTypedStorage) checkContext(ctx context.Context) {
	storage.actualNamespace, storage.namespacePresent = request.NamespaceFrom(ctx)
	storage.actualTenant, storage.tenantPresent = request.TenantFrom(ctx)
}

func bodyOrDie(response *http.Response) string {
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		panic(err)
	}
	return string(body)
}

func extractBody(response *http.Response, object runtime.Object) (string, error) {
	return extractBodyDecoder(response, object, codec)
}

func extractBodyDecoder(response *http.Response, object runtime.Object, decoder runtime.Decoder) (string, error) {
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return string(body), err
	}
	return string(body), runtime.DecodeInto(decoder, body, object)
}

func extractBodyObject(response *http.Response, decoder runtime.Decoder) (runtime.Object, string, error) {
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, string(body), err
	}
	obj, err := runtime.Decode(decoder, body)
	return obj, string(body), err
}

func TestNotFound(t *testing.T) {
	type T struct {
		Method string
		Path   string
		Status int
	}
	cases := map[string]T{
		// Positive checks to make sure everything is wired correctly
		"groupless GET root":       {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusOK},
		"groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK},

		"groupless GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound},

		"groupless root PATCH method":                 {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"groupless root GET missing storage":          {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound},
		"groupless root GET with extra segment":       {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"groupless root DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"groupless root DELETE with extra segment":    {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"groupless root PUT without extra segment":    {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"groupless root PUT with extra segment":       {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"groupless root watch missing storage":        {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError},

		"groupless namespaced PATCH method":                 {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"groupless namespaced GET long prefix":              {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound},
		"groupless namespaced GET missing storage":          {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound},
		"groupless namespaced GET with extra segment":       {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"groupless namespaced POST with extra segment":      {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
		"groupless namespaced DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"groupless namespaced DELETE with extra segment":    {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"groupless namespaced PUT without extra segment":    {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"groupless namespaced PUT with extra segment":       {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"groupless namespaced watch missing storage":        {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError},
		"groupless namespaced watch with bad method":        {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
		"groupless namespaced watch param with bad method":  {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed},

		// Positive checks to make sure everything is wired correctly
		"GET root": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK},
		// TODO: JTL: "GET root item":       {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusOK},
		"GET namespaced": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK},
		// TODO: JTL: "GET namespaced item": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusOK},

		"GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound},

		"root PATCH method":           {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"root GET missing storage":    {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound},
		"root GET with extra segment": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		// TODO: JTL: "root POST with extra segment":      {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusMethodNotAllowed},
		"root DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"root DELETE with extra segment":    {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"root PUT without extra segment":    {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"root PUT with extra segment":       {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"root watch missing storage":        {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError},
		// TODO: JTL: "root watch with bad method":        {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simpleroot/bar", http.StatusMethodNotAllowed},

		"namespaced PATCH method":                 {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"namespaced GET long prefix":              {"GET", "/" + prefix + "/", http.StatusNotFound},
		"namespaced GET missing storage":          {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound},
		"namespaced GET with extra segment":       {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"namespaced POST with extra segment":      {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
		"namespaced DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"namespaced DELETE with extra segment":    {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"namespaced PUT without extra segment":    {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"namespaced PUT with extra segment":       {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"namespaced watch missing storage":        {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError},
		"namespaced watch with bad method":        {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
		"namespaced watch param with bad method":  {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed},
	}
	handler := handle(map[string]rest.Storage{
		"simples":     &SimpleRESTStorage{},
		"simpleroots": &SimpleRESTStorage{},
	})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}
	for k, v := range cases {
		request, err := http.NewRequest(v.Method, server.URL+v.Path, nil)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		response, err := client.Do(request)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		if response.StatusCode != v.Status {
			t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v.Method, k, response)
		}
	}
}

// This test is about the APIs with multi-tenancy changes in, while the previous TestNotFound() test is about the backward compatibility with the API server
// before multi-tenancy change.
// TestNotFound() should be removed when we no longer support the legacy apis before multi-tenancy.

// Many test cases below in this test file have two versions. One for backward compatibility with the API server befor Multi-Teanacy change, and the other
// (with WithMultiTenancy in the test case names) is for the behavior in a Multi-Tenancy cluster.
// The legacy test cases should be removed when we no longer support the legacy apis.
func TestNotFoundWithMultiTenancy(t *testing.T) {
	type T struct {
		Method string
		Path   string
		Status int
	}
	cases := map[string]T{
		// Positive checks to make sure everything is wired correctly
		"groupless GET root":       {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusOK},
		"groupless GET tenanted":   {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/simpletenants", http.StatusOK},
		"groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/ns/simples", http.StatusOK},

		"groupless GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound},

		"groupless root PATCH method":                 {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"groupless root GET missing storage":          {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound},
		"groupless root GET with extra segment":       {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"groupless root DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"groupless root DELETE with extra segment":    {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"groupless root PUT without extra segment":    {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"groupless root PUT with extra segment":       {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"groupless root watch missing storage":        {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError},

		"groupless tenanted PATCH method":                 {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/simpletenants", http.StatusMethodNotAllowed},
		"groupless tenanted GET long prefix":              {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound},
		"groupless tenanted GET missing storage":          {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound},
		"groupless tenanted GET with extra segment":       {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/simpletenants/bar/baz", http.StatusNotFound},
		"groupless tenanted POST with extra segment":      {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/simpletenants/bar", http.StatusMethodNotAllowed},
		"groupless tenanted DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/simpletenants", http.StatusMethodNotAllowed},
		"groupless tenanted DELETE with extra segment":    {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/simpletenants/bar/baz", http.StatusNotFound},
		"groupless tenanted PUT without extra segment":    {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/simpletenants", http.StatusMethodNotAllowed},
		"groupless tenanted PUT with extra segment":       {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/simpletenants/bar/baz", http.StatusNotFound},
		"groupless tenanted watch missing storage":        {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError},
		"groupless tenanted watch with bad method":        {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/tenants/te/simpletenants/bar", http.StatusMethodNotAllowed},
		"groupless tenanted watch param with bad method":  {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/simpletenants/bar?watch=true", http.StatusMethodNotAllowed},

		"groupless namespaced PATCH method":                 {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"groupless namespaced GET long prefix":              {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound},
		"groupless namespaced GET missing storage":          {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound},
		"groupless namespaced GET with extra segment":       {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"groupless namespaced POST with extra segment":      {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
		"groupless namespaced DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"groupless namespaced DELETE with extra segment":    {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"groupless namespaced PUT without extra segment":    {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"groupless namespaced PUT with extra segment":       {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"groupless namespaced watch missing storage":        {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError},
		"groupless namespaced watch with bad method":        {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/tenants/te/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
		"groupless namespaced watch param with bad method":  {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed},

		// Positive checks to make sure everything is wired correctly
		"GET root":            {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK},
		"GET root item":       {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusOK},
		"GET tenanted":        {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants", http.StatusOK},
		"GET tenanted item":   {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants/bar", http.StatusOK},
		"GET namespaced":      {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples", http.StatusOK},
		"GET namespaced item": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar", http.StatusOK},

		"GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound},

		"root PATCH method":                 {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"root GET missing storage":          {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound},
		"root GET with extra segment":       {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"root POST with extra segment":      {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusMethodNotAllowed},
		"root DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"root DELETE with extra segment":    {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"root PUT without extra segment":    {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
		"root PUT with extra segment":       {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
		"root watch missing storage":        {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError},
		"root watch with bad method":        {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simpleroots/bar", http.StatusMethodNotAllowed},

		"tenanted PATCH method":                 {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants", http.StatusMethodNotAllowed},
		"tenanted GET long prefix":              {"GET", "/" + prefix + "/", http.StatusNotFound},
		"tenanted GET missing storage":          {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound},
		"tenanted GET with extra segment":       {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants/bar/baz", http.StatusNotFound},
		"tenanted POST with extra segment":      {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants/bar", http.StatusMethodNotAllowed},
		"tenanted DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants", http.StatusMethodNotAllowed},
		"tenanted DELETE with extra segment":    {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants/bar/baz", http.StatusNotFound},
		"tenanted PUT without extra segment":    {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants", http.StatusMethodNotAllowed},
		"tenanted PUT with extra segment":       {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants/bar/baz", http.StatusNotFound},
		"tenanted watch missing storage":        {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError},
		"tenanted watch with bad method":        {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/tenants/te/simpletenants/bar", http.StatusMethodNotAllowed},
		"tenanted watch param with bad method":  {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/simpletenants/bar?watch=true", http.StatusMethodNotAllowed},

		"namespaced PATCH method":                 {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"namespaced GET long prefix":              {"GET", "/" + prefix + "/", http.StatusNotFound},
		"namespaced GET missing storage":          {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound},
		"namespaced GET with extra segment":       {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"namespaced POST with extra segment":      {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
		"namespaced DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"namespaced DELETE with extra segment":    {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"namespaced PUT without extra segment":    {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples", http.StatusMethodNotAllowed},
		"namespaced PUT with extra segment":       {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar/baz", http.StatusNotFound},
		"namespaced watch missing storage":        {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError},
		"namespaced watch with bad method":        {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/tenants/te/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
		"namespaced watch param with bad method":  {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed},
	}
	handler := handle(map[string]rest.Storage{
		"simples":       &SimpleRESTStorage{},
		"simpletenants": &SimpleTenantRESTStorage{},
		"simpleroots":   &SimpleRootRESTStorage{},
	})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}
	for k, v := range cases {
		request, err := http.NewRequest(v.Method, server.URL+v.Path, nil)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		response, err := client.Do(request)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		if response.StatusCode != v.Status {
			t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v.Method, k, response)
		}
	}
}

type UnimplementedRESTStorage struct{}

func (UnimplementedRESTStorage) NamespaceScoped() bool {
	return true
}

func (UnimplementedRESTStorage) TenantScoped() bool {
	return true
}

func (UnimplementedRESTStorage) New() runtime.Object {
	return &genericapitesting.Simple{}
}

// TestUnimplementedRESTStorage ensures that if a rest.Storage does not implement a given
// method, that it is literally not registered with the server.  In the past,
// we registered everything, and returned method not supported if it didn't support
// a verb.  Now we literally do not register a storage if it does not implement anything.
// TODO: in future, we should update proxy/redirect
func TestUnimplementedRESTStorage(t *testing.T) {
	type T struct {
		Method  string
		Path    string
		ErrCode int
	}
	cases := map[string]T{
		"groupless GET object":    {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound},
		"groupless GET list":      {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound},
		"groupless POST list":     {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound},
		"groupless PUT object":    {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound},
		"groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound},
		"groupless watch list":    {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo", http.StatusNotFound},
		"groupless watch object":  {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound},
		"groupless proxy object":  {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/proxy/foo/bar", http.StatusNotFound},

		"GET object":    {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound},
		"GET list":      {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound},
		"POST list":     {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound},
		"PUT object":    {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound},
		"DELETE object": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound},
		"watch list":    {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/foo", http.StatusNotFound},
		"watch object":  {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound},
		"proxy object":  {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/proxy/foo/bar", http.StatusNotFound},
	}
	handler := handle(map[string]rest.Storage{
		"foo": UnimplementedRESTStorage{},
	})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}
	for k, v := range cases {
		request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`)))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		response, err := client.Do(request)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		defer response.Body.Close()
		data, err := ioutil.ReadAll(response.Body)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if response.StatusCode != v.ErrCode {
			t.Errorf("%s: expected %d for %s, Got %s", k, v.ErrCode, v.Method, string(data))
			continue
		}
	}
}

type OnlyGetRESTStorage struct {
	UnimplementedRESTStorage
}

func (OnlyGetRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) {
	return nil, nil
}

func (OnlyGetRESTStorage) NewList() runtime.Object {
	return &genericapitesting.SimpleList{}
}

func (OnlyGetRESTStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
	return nil, nil
}

// TestSomeUnimplementedRESTStorage ensures that if a rest.Storage does
// not implement a given method, that it is literally not registered
// with the server. We need to have at least one verb supported inorder
// to get a MethodNotAllowed rather than NotFound error.
func TestSomeUnimplementedRESTStorage(t *testing.T) {
	type T struct {
		Method  string
		Path    string
		ErrCode int
	}

	cases := map[string]T{
		"groupless POST list":         {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed},
		"groupless PUT object":        {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
		"groupless DELETE object":     {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
		"groupless DELETE collection": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed},
		"POST list":                   {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed},
		"PUT object":                  {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
		"DELETE object":               {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
		"DELETE collection":           {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed},
	}
	handler := handle(map[string]rest.Storage{
		"foo": OnlyGetRESTStorage{},
	})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}
	for k, v := range cases {
		request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`)))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		response, err := client.Do(request)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		defer response.Body.Close()
		data, err := ioutil.ReadAll(response.Body)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if response.StatusCode != v.ErrCode {
			t.Errorf("%s: expected %d for %s, Got %s", k, v.ErrCode, v.Method, string(data))
			continue
		}
	}
}

func TestSomeUnimplementedRESTStorageWithMultiTenancy(t *testing.T) {
	type T struct {
		Method  string
		Path    string
		ErrCode int
	}

	cases := map[string]T{
		"groupless POST list":         {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/default/foo", http.StatusMethodNotAllowed},
		"groupless PUT object":        {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
		"groupless DELETE object":     {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
		"groupless DELETE collection": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/te/namespaces/default/foo", http.StatusMethodNotAllowed},
		"POST list":                   {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/default/foo", http.StatusMethodNotAllowed},
		"PUT object":                  {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
		"DELETE object":               {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
		"DELETE collection":           {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/te/namespaces/default/foo", http.StatusMethodNotAllowed},
	}
	handler := handle(map[string]rest.Storage{
		"foo": OnlyGetRESTStorage{},
	})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}
	for k, v := range cases {
		request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`)))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		response, err := client.Do(request)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		defer response.Body.Close()
		data, err := ioutil.ReadAll(response.Body)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if response.StatusCode != v.ErrCode {
			t.Errorf("%s: expected %d for %s, Got %s", k, v.ErrCode, v.Method, string(data))
			continue
		}
	}
}

func TestList(t *testing.T) {
	testCases := []struct {
		url       string
		namespace string
		selfLink  string
		legacy    bool
		label     string
		field     string
	}{
		// Groupless API

		// legacy namespace param is ignored
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
			label:     "a=b",
			field:     "c=d",
		},
		// legacy api version is honored
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
			namespace: "other",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
			legacy:    true,
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
			namespace: "other",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
			legacy:    true,
			label:     "a=b",
			field:     "c=d",
		},
		// list items across all namespaces
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
		},
		// list items in a namespace in the path
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
			namespace: "default",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
			namespace: "other",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
			namespace: "other",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
			label:     "a=b",
			field:     "c=d",
		},
		// list items across all namespaces
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
		},

		// Group API

		// legacy namespace param is ignored
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			legacy:    true,
			label:     "a=b",
			field:     "c=d",
		},
		// legacy api version is honored
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple",
			namespace: "other",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple",
			legacy:    true,
		},
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
			namespace: "other",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple",
			legacy:    true,
			label:     "a=b",
			field:     "c=d",
		},
		// list items across all namespaces
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			legacy:    true,
		},
		// list items in a namespace in the path
		{
			url:       "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/default/simple",
			namespace: "default",
			selfLink:  "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/default/simple",
		},
		{
			url:       "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple",
			namespace: "other",
			selfLink:  "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple",
		},
		{
			url:       "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
			namespace: "other",
			selfLink:  "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple",
			label:     "a=b",
			field:     "c=d",
		},
		// list items across all namespaces
		{
			url:       "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple",
			namespace: "",
			selfLink:  "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple",
		},
	}
	for i, testCase := range testCases {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace}
		storage["simple"] = &simpleStorage
		selfLinker := &setTestSelfLinker{
			t:           t,
			namespace:   testCase.namespace,
			expectedSet: testCase.selfLink,
		}
		var handler = handleInternal(storage, admissionControl, selfLinker, nil)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testCase.url)
		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Errorf("%d: unexpected error: %v", i, err)
				continue
			}
			t.Logf("%d: body: %s", i, string(body))
			continue
		}
		// TODO: future, restore get links
		if !selfLinker.called {
			t.Errorf("%d: never set self link", i)
		}
		if !simpleStorage.namespacePresent {
			t.Errorf("%d: namespace not set", i)
		} else if simpleStorage.actualNamespace != testCase.namespace {
			t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace)
		}
		if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label {
			t.Errorf("%d: unexpected label selector: expected=%v got=%v", i, testCase.label, simpleStorage.requestedLabelSelector)
		}
		if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field {
			t.Errorf("%d: unexpected field selector: expected=%v got=%v", i, testCase.field, simpleStorage.requestedFieldSelector)
		}
	}
}

func TestListNamespaceScopeWithMultiTenancy(t *testing.T) {
	testCases := []struct {
		url       string
		tenant    string
		namespace string
		selfLink  string
		legacy    bool
		label     string
		field     string
	}{
		// Groupless API

		// legacy namespace param is ignored
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
			label:     "a=b",
			field:     "c=d",
		},
		// legacy api version is honored
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			tenant:    "test",
			namespace: "other",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			legacy:    true,
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:    "test",
			namespace: "other",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			legacy:    true,
			label:     "a=b",
			field:     "c=d",
		},
		// list items in a namespace in the path
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/default/simple",
			tenant:    "test",
			namespace: "default",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/default/simple",
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			tenant:    "test",
			namespace: "other",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/other/simple",
		},
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:    "test",
			namespace: "other",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			label:     "a=b",
			field:     "c=d",
		},
		// list items across all namespaces of a tenant
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simple",
			tenant:    "test",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simple",
			legacy:    true,
		},
		// list items across all namespaces of all tenants
		{
			url:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
			legacy:    true,
		},

		// Group API

		// legacy namespace param is ignored
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			legacy:    true,
			label:     "a=b",
			field:     "c=d",
		},
		// legacy api version is honored
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			legacy:    true,
		},
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			tenant:    "test",
			namespace: "other",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			legacy:    true,
		},
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:    "test",
			namespace: "other",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			legacy:    true,
			label:     "a=b",
			field:     "c=d",
		},
		// list items in a namespace in the path
		{
			url:       "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/namespaces/default/simple",
			tenant:    "test",
			namespace: "default",
			selfLink:  "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/namespaces/default/simple",
		},
		{
			url:       "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			tenant:    "test",
			namespace: "other",
			selfLink:  "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/namespaces/other/simple",
		},
		{
			url:       "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:    "test",
			namespace: "other",
			selfLink:  "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/namespaces/other/simple",
			label:     "a=b",
			field:     "c=d",
		},
		// list items across all namespaces of a tenant
		{
			url:       "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/simple",
			tenant:    "test",
			namespace: "",
			selfLink:  "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/simple",
		},
		// list items across all namespaces of all the tenants
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			tenant:    "",
			namespace: "",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
		},
	}
	for i, testCase := range testCases {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace, expectedResourceTenant: testCase.tenant}
		storage["simple"] = &simpleStorage
		selfLinker := &setTestSelfLinker{
			t:           t,
			tenant:      testCase.tenant,
			namespace:   testCase.namespace,
			expectedSet: testCase.selfLink,
		}
		var handler = handleInternal(storage, admissionControl, selfLinker, nil)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testCase.url)

		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Errorf("%d: unexpected error: %v", i, err)
				continue
			}
			t.Logf("%d: body: %s", i, string(body))
			continue
		}
		// TODO: future, restore get links
		if !selfLinker.called {
			t.Errorf("%d: never set self link", i)
		}
		if !simpleStorage.tenantPresent {
			fmt.Println(testCase.url)
			t.Errorf("%d: tenant not set", i)
		} else if simpleStorage.actualTenant != testCase.tenant {
			t.Errorf("%d: %q unexpected resource tenant: %s, expected %s", i, testCase.url, simpleStorage.actualTenant, testCase.tenant)
		}
		if !simpleStorage.namespacePresent {
			fmt.Println(testCase.url)
			t.Errorf("%d: namespace not set", i)
		} else if simpleStorage.actualNamespace != testCase.namespace {
			t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace)
		}
		if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label {
			t.Errorf("%d: unexpected label selector: expected=%v got=%v", i, testCase.label, simpleStorage.requestedLabelSelector)
		}
		if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field {
			t.Errorf("%d: unexpected field selector: expected=%v got=%v", i, testCase.field, simpleStorage.requestedFieldSelector)
		}
	}
}

func TestListTenantScopeWithMultiTenancy(t *testing.T) {
	testCases := []struct {
		url      string
		tenant   string
		selfLink string
		legacy   bool
		label    string
		field    string
	}{
		// Groupless API

		// legacy api version is honored
		{
			url:      "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpletenant",
			tenant:   "",
			selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpletenant",
			legacy:   true,
		},
		{
			url:      "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simpletenant",
			tenant:   "test",
			selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simpletenant",
			legacy:   true,
		},
		{
			url:      "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simpletenant?labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:   "test",
			selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simpletenant",
			legacy:   true,
			label:    "a=b",
			field:    "c=d",
		},
		// list items under a tenant
		{
			url:      "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simpletenant",
			tenant:   "test",
			selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simpletenant",
		},
		{
			url:      "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simpletenant?labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:   "test",
			selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/simpletenant",
			label:    "a=b",
			field:    "c=d",
		},

		// list items across all tenants
		{
			url:      "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpletenant",
			tenant:   "",
			selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpletenant",
			legacy:   true,
		},

		// Group API
		// legacy api version is honored
		{
			url:      "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpletenant",
			tenant:   "",
			selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpletenant",
			legacy:   true,
		},
		{
			url:      "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/simpletenant",
			tenant:   "test",
			selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/simpletenant",
			legacy:   true,
		},
		{
			url:      "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/simpletenant?labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:   "test",
			selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/simpletenant",
			legacy:   true,
			label:    "a=b",
			field:    "c=d",
		},
		// list items under a tenant
		{
			url:      "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/simpletenant",
			tenant:   "test",
			selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/simpletenant",
		},
		{
			url:      "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/simpletenant?labelSelector=a%3Db&fieldSelector=c%3Dd",
			tenant:   "test",
			selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/simpletenant",
			label:    "a=b",
			field:    "c=d",
		},
		// list items across  all the tenants
		{
			url:      "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpletenant",
			tenant:   "",
			selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpletenant",
		},
	}
	for i, testCase := range testCases {
		storage := map[string]rest.Storage{}
		simpleTenantStorage := SimpleTenantRESTStorage{SimpleRESTStorage{expectedResourceTenant: testCase.tenant}}
		storage["simpletenant"] = &simpleTenantStorage
		selfLinker := &setTestSelfLinker{
			t:           t,
			tenant:      testCase.tenant,
			namespace:   "",
			expectedSet: testCase.selfLink,
		}
		var handler = handleInternal(storage, admissionControl, selfLinker, nil)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testCase.url)

		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Errorf("%d: unexpected error: %v", i, err)
				continue
			}
			t.Logf("%d: body: %s", i, string(body))
			continue
		}
		// TODO: future, restore get links
		if !selfLinker.called {
			t.Errorf("%d: never set self link", i)
		}
		if !simpleTenantStorage.SimpleRESTStorage.tenantPresent {
			fmt.Println(testCase.url)
			t.Errorf("%d: tenant not set", i)
		} else if simpleTenantStorage.SimpleRESTStorage.actualTenant != testCase.tenant {
			t.Errorf("%d: %q unexpected resource tenant: %s, expected %s", i, testCase.url, simpleTenantStorage.SimpleRESTStorage.actualTenant, testCase.tenant)
		}
		if simpleTenantStorage.SimpleRESTStorage.requestedLabelSelector == nil || simpleTenantStorage.SimpleRESTStorage.requestedLabelSelector.String() != testCase.label {
			t.Errorf("%d: unexpected label selector: expected=%v got=%v", i, testCase.label, simpleTenantStorage.SimpleRESTStorage.requestedLabelSelector)
		}
		if simpleTenantStorage.SimpleRESTStorage.requestedFieldSelector == nil || simpleTenantStorage.SimpleRESTStorage.requestedFieldSelector.String() != testCase.field {
			t.Errorf("%d: unexpected field selector: expected=%v got=%v", i, testCase.field, simpleTenantStorage.SimpleRESTStorage.requestedFieldSelector)
		}
	}
}

func TestRequestsWithInvalidQuery(t *testing.T) {
	storage := map[string]rest.Storage{}

	storage["simple"] = &SimpleRESTStorage{expectedResourceNamespace: "default"}
	storage["withoptions"] = GetWithOptionsRESTStorage{}

	var handler = handleInternal(storage, admissionControl, selfLinker, nil)
	server := httptest.NewServer(handler)
	defer server.Close()

	for i, test := range []struct {
		postfix string
		method  string
	}{
		{"/simple?labelSelector=<invalid>", http.MethodGet},
		{"/simple/foo?gracePeriodSeconds=<invalid>", http.MethodDelete},
		// {"/simple?labelSelector=<value>", http.MethodDelete}, TODO: implement DeleteCollection in  SimpleRESTStorage
		// {"/simple/foo?export=<invalid>", http.MethodGet}, TODO: there is no invalid bool in conversion. Should we be more strict?
		// {"/simple/foo?resourceVersion=<invalid>", http.MethodGet}, TODO: there is no invalid resourceVersion. Should we be more strict?
		// {"/withoptions?labelSelector=<invalid>", http.MethodGet}, TODO: SimpleGetOptions is always valid. Add more validation that can fail.
	} {
		baseURL := server.URL + "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default"
		url := baseURL + test.postfix
		r, err := http.NewRequest(test.method, url, nil)
		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		resp, err := http.DefaultClient.Do(r)
		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusBadRequest {
			t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, url, http.StatusBadRequest, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Errorf("%d: unexpected error: %v", i, err)
				continue
			}
			t.Logf("%d: body: %s", i, string(body))
		}
	}
}

func TestRequestsWithInvalidQueryWithMultiTenancy(t *testing.T) {
	storage := map[string]rest.Storage{}

	storage["simple"] = &SimpleRESTStorage{expectedResourceNamespace: "default", expectedResourceTenant: "test"}
	storage["withoptions"] = GetWithOptionsRESTStorage{}

	var handler = handleInternal(storage, admissionControl, selfLinker, nil)
	server := httptest.NewServer(handler)
	defer server.Close()

	for i, test := range []struct {
		postfix string
		method  string
	}{
		{"/simple?labelSelector=<invalid>", http.MethodGet},
		{"/simple/foo?gracePeriodSeconds=<invalid>", http.MethodDelete},
		// {"/simple?labelSelector=<value>", http.MethodDelete}, TODO: implement DeleteCollection in  SimpleRESTStorage
		// {"/simple/foo?export=<invalid>", http.MethodGet}, TODO: there is no invalid bool in conversion. Should we be more strict?
		// {"/simple/foo?resourceVersion=<invalid>", http.MethodGet}, TODO: there is no invalid resourceVersion. Should we be more strict?
		// {"/withoptions?labelSelector=<invalid>", http.MethodGet}, TODO: SimpleGetOptions is always valid. Add more validation that can fail.
	} {
		baseURL := server.URL + "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/default"
		url := baseURL + test.postfix
		r, err := http.NewRequest(test.method, url, nil)
		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		resp, err := http.DefaultClient.Do(r)
		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusBadRequest {
			t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, url, http.StatusBadRequest, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Errorf("%d: unexpected error: %v", i, err)
				continue
			}
			t.Logf("%d: body: %s", i, string(body))
		}
	}
}

func TestListCompression(t *testing.T) {
	testCases := []struct {
		url            string
		namespace      string
		selfLink       string
		legacy         bool
		label          string
		field          string
		acceptEncoding string
	}{
		// list items in a namespace in the path
		{
			url:            "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
			namespace:      "default",
			selfLink:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
			acceptEncoding: "",
		},
		{
			url:            "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
			namespace:      "default",
			selfLink:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
			acceptEncoding: "gzip",
		},
	}
	for i, testCase := range testCases {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{
			expectedResourceNamespace: testCase.namespace,
			list: []genericapitesting.Simple{
				{Other: strings.Repeat("0123456789abcdef", (128*1024/16)+1)},
			},
		}
		storage["simple"] = &simpleStorage
		selfLinker := &setTestSelfLinker{
			t:           t,
			namespace:   testCase.namespace,
			expectedSet: testCase.selfLink,
		}
		var handler = handleInternal(storage, admissionControl, selfLinker, nil)

		handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver())

		server := httptest.NewServer(handler)

		defer server.Close()

		req, err := http.NewRequest("GET", server.URL+testCase.url, nil)
		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		// It's necessary to manually set Accept-Encoding here
		// to prevent http.DefaultClient from automatically
		// decoding responses
		req.Header.Set("Accept-Encoding", testCase.acceptEncoding)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Errorf("%d: unexpected error: %v", i, err)
				continue
			}
			t.Logf("%d: body: %s", i, string(body))
			continue
		}
		// TODO: future, restore get links
		if !selfLinker.called {
			t.Errorf("%d: never set self link", i)
		}
		if !simpleStorage.namespacePresent {
			t.Errorf("%d: namespace not set", i)
		} else if simpleStorage.actualNamespace != testCase.namespace {
			t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace)
		}
		if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label {
			t.Errorf("%d: unexpected label selector: %v", i, simpleStorage.requestedLabelSelector)
		}
		if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field {
			t.Errorf("%d: unexpected field selector: %v", i, simpleStorage.requestedFieldSelector)
		}

		var decoder *json.Decoder
		if testCase.acceptEncoding == "gzip" {
			gzipReader, err := gzip.NewReader(resp.Body)
			if err != nil {
				t.Fatalf("unexpected error creating gzip reader: %v", err)
			}
			decoder = json.NewDecoder(gzipReader)
		} else {
			decoder = json.NewDecoder(resp.Body)
		}
		var itemOut genericapitesting.SimpleList
		err = decoder.Decode(&itemOut)
		if err != nil {
			t.Errorf("failed to read response body as SimpleList: %v", err)
		}
	}
}

func TestListCompressionWithMultiTenancy(t *testing.T) {
	testCases := []struct {
		url            string
		tenant         string
		namespace      string
		selfLink       string
		legacy         bool
		label          string
		field          string
		acceptEncoding string
	}{
		// list items in a namespace in the path
		{
			url:            "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/default/simple",
			tenant:         "test",
			namespace:      "default",
			selfLink:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/default/simple",
			acceptEncoding: "",
		},
		{
			url:            "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/default/simple",
			tenant:         "test",
			namespace:      "default",
			selfLink:       "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/tenants/test/namespaces/default/simple",
			acceptEncoding: "gzip",
		},
	}
	for i, testCase := range testCases {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{
			expectedResourceNamespace: testCase.namespace,
			list: []genericapitesting.Simple{
				{Other: strings.Repeat("0123456789abcdef", (128*1024/16)+1)},
			},
		}
		storage["simple"] = &simpleStorage
		selfLinker := &setTestSelfLinker{
			t:           t,
			tenant:      testCase.tenant,
			namespace:   testCase.namespace,
			expectedSet: testCase.selfLink,
		}
		var handler = handleInternal(storage, admissionControl, selfLinker, nil)

		handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver())

		server := httptest.NewServer(handler)

		defer server.Close()

		req, err := http.NewRequest("GET", server.URL+testCase.url, nil)
		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		// It's necessary to manually set Accept-Encoding here
		// to prevent http.DefaultClient from automatically
		// decoding responses
		req.Header.Set("Accept-Encoding", testCase.acceptEncoding)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Errorf("%d: unexpected error: %v", i, err)
			continue
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Errorf("%d: unexpected error: %v", i, err)
				continue
			}
			t.Logf("%d: body: %s", i, string(body))
			continue
		}
		// TODO: future, restore get links
		if !selfLinker.called {
			t.Errorf("%d: never set self link", i)
		}
		if !simpleStorage.tenantPresent {
			t.Errorf("%d: tenant not set", i)
		} else if simpleStorage.actualTenant != testCase.tenant {
			t.Errorf("%d: %q unexpected resource tenant: %s", i, testCase.url, simpleStorage.actualTenant)
		}
		if !simpleStorage.namespacePresent {
			t.Errorf("%d: namespace not set", i)
		} else if simpleStorage.actualNamespace != testCase.namespace {
			t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace)
		}
		if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label {
			t.Errorf("%d: unexpected label selector: %v", i, simpleStorage.requestedLabelSelector)
		}
		if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field {
			t.Errorf("%d: unexpected field selector: %v", i, simpleStorage.requestedFieldSelector)
		}

		var decoder *json.Decoder
		if testCase.acceptEncoding == "gzip" {
			gzipReader, err := gzip.NewReader(resp.Body)
			if err != nil {
				t.Fatalf("unexpected error creating gzip reader: %v", err)
			}
			decoder = json.NewDecoder(gzipReader)
		} else {
			decoder = json.NewDecoder(resp.Body)
		}
		var itemOut genericapitesting.SimpleList
		err = decoder.Decode(&itemOut)
		if err != nil {
			t.Errorf("failed to read response body as SimpleList: %v", err)
		}
	}
}

func TestLogs(t *testing.T) {
	handler := handle(map[string]rest.Storage{})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	request, err := http.NewRequest("GET", server.URL+"/logs", nil)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	t.Logf("Data: %s", string(body))
}

func TestErrorList(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		errors: map[string]error{"list": fmt.Errorf("test Error")},
	}
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if resp.StatusCode != http.StatusInternalServerError {
		t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusInternalServerError, resp)
	}
}

func TestNonEmptyList(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		list: []genericapitesting.Simple{
			{
				ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "other"},
				Other:      "foo",
			},
		},
	}
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if resp.StatusCode != http.StatusOK {
		t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		t.Logf("Data: %s", string(body))
	}

	var listOut genericapitesting.SimpleList
	body, err := extractBody(resp, &listOut)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	t.Log(body)

	if len(listOut.Items) != 1 {
		t.Errorf("Unexpected response: %#v", listOut)
		return
	}
	if listOut.Items[0].Other != simpleStorage.list[0].Other {
		t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
	}
	if listOut.SelfLink != "/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/simple" {
		t.Errorf("unexpected list self link: %#v", listOut)
	}
	expectedSelfLink := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple/something"
	if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
		t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
	}
}

func TestNonEmptyListWithMultiTenancy(t *testing.T) {
	testCases := []struct {
		url        string
		objectMeta metav1.ObjectMeta
		other      string
		selfLink   string
	}{
		{
			url:        "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			objectMeta: metav1.ObjectMeta{Name: "something", Namespace: "other", Tenant: "else"},
			other:      "foo",
			selfLink:   "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/else/namespaces/other/simple/something",
		},
	}

	for _, testCase := range testCases {

		storage := map[string]rest.Storage{}

		simpleStorage := SimpleRESTStorage{
			list: []genericapitesting.Simple{
				{
					ObjectMeta: testCase.objectMeta,
					Other:      testCase.other,
				},
			},
		}

		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testCase.url)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			t.Logf("Data: %s", string(body))
		}

		var listOut genericapitesting.SimpleList
		body, err := extractBody(resp, &listOut)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		t.Log(body)

		if len(listOut.Items) != 1 {
			t.Errorf("Unexpected response: %#v", listOut)
			return
		}
		if listOut.Items[0].Other != simpleStorage.list[0].Other {
			t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
		}
		if listOut.SelfLink != "/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/simple" {
			t.Errorf("unexpected list self link: %#v", listOut)
		}

		if listOut.Items[0].ObjectMeta.SelfLink != testCase.selfLink {
			t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, testCase.selfLink)
		}
	}
}

func TestSelfLinkSkipsEmptyName(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		list: []genericapitesting.Simple{
			{
				ObjectMeta: metav1.ObjectMeta{Namespace: "other"},
				Other:      "foo",
			},
		},
	}
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if resp.StatusCode != http.StatusOK {
		t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		t.Logf("Data: %s", string(body))
	}
	var listOut genericapitesting.SimpleList
	body, err := extractBody(resp, &listOut)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if len(listOut.Items) != 1 {
		t.Errorf("Unexpected response: %#v", listOut)
		return
	}
	if listOut.Items[0].Other != simpleStorage.list[0].Other {
		t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
	}
	if listOut.SelfLink != "/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/simple" {
		t.Errorf("unexpected list self link: %#v", listOut)
	}
	expectedSelfLink := ""
	if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
		t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
	}
}

func TestSelfLinkSkipsEmptyNameWithMultiTenancy(t *testing.T) {
	testCases := []struct {
		url        string
		objectMeta metav1.ObjectMeta
		other      string
		selfLink   string
	}{
		{
			url:        "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple",
			objectMeta: metav1.ObjectMeta{Namespace: "other", Tenant: "else"},
			other:      "foo",
			selfLink:   "",
		},
	}

	for _, testCase := range testCases {

		storage := map[string]rest.Storage{}

		simpleStorage := SimpleRESTStorage{
			list: []genericapitesting.Simple{
				{
					ObjectMeta: testCase.objectMeta,
					Other:      testCase.other,
				},
			},
		}

		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testCase.url)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			t.Logf("Data: %s", string(body))
		}

		var listOut genericapitesting.SimpleList
		body, err := extractBody(resp, &listOut)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		t.Log(body)

		if len(listOut.Items) != 1 {
			t.Errorf("Unexpected response: %#v", listOut)
			return
		}
		if listOut.Items[0].Other != simpleStorage.list[0].Other {
			t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
		}
		if listOut.SelfLink != "/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/simple" {
			t.Errorf("unexpected list self link: %#v", listOut)
		}

		if listOut.Items[0].ObjectMeta.SelfLink != testCase.selfLink {
			t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, testCase.selfLink)
		}
	}
}

func TestRootSelfLink(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := GetWithOptionsRootRESTStorage{
		SimpleTypedStorage: &SimpleTypedStorage{
			baseType: &genericapitesting.SimpleRoot{}, // a root scoped type
			item: &genericapitesting.SimpleRoot{
				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
				Other:      "foo",
			},
		},
		takesPath: "atAPath",
	}
	storage["simple"] = &simpleStorage
	storage["simple/sub"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	testCases := []struct {
		url      string
		selfLink string
	}{
		{
			url:      server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple/foo",
			selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple/foo",
		},
		{
			url:      server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple/foo/sub",
			selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple/foo/sub",
		},
	}

	for _, test := range testCases {
		resp, err := http.Get(test.url)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			t.Logf("Data: %s", string(body))
		}
		var out genericapitesting.SimpleRoot
		if _, err := extractBody(resp, &out); err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if out.SelfLink != test.selfLink {
			t.Errorf("unexpected self link: %#v", out.SelfLink)
		}
	}
}

func TestMetadata(t *testing.T) {
	simpleStorage := &MetadataRESTStorage{&SimpleRESTStorage{}, []string{"text/plain"}}
	h := handle(map[string]rest.Storage{"simple": simpleStorage})
	ws := h.(*defaultAPIServer).container.RegisteredWebServices()
	if len(ws) == 0 {
		t.Fatal("no web services registered")
	}
	matches := map[string]int{}
	for _, w := range ws {
		for _, r := range w.Routes() {
			t.Logf("%v %v %#v", r.Method, r.Path, r.Produces)
			s := strings.Join(r.Produces, ",")
			i := matches[s]
			matches[s] = i + 1
		}
	}
	cs := []func() bool{
		func() bool {
			return matches["text/plain,application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0
		},
		func() bool {
			return matches["application/json,application/yaml,application/vnd.kubernetes.protobuf,application/json;stream=watch,application/vnd.kubernetes.protobuf;stream=watch"] == 0
		},
		func() bool {
			return matches["application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0
		},
		func() bool {
			return len(matches) != 4
		},
	}
	for i, c := range cs {
		if c() {
			t.Errorf("[%d]unexpected mime types: %#v", i, matches)
		}
	}
}

func TestExport(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			ObjectMeta: metav1.ObjectMeta{
				ResourceVersion:   "1234",
				CreationTimestamp: metav1.NewTime(time.Unix(10, 10)),
			},
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
		name:        "id",
		namespace:   "default",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id?export=true")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		data, _ := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		t.Fatalf("unexpected response: %#v\n%s\n", resp, string(data))
	}
	var itemOut genericapitesting.Simple
	body, err := extractBody(resp, &itemOut)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	if itemOut.Name != simpleStorage.item.Name {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
	}
	if itemOut.Other != "exported" {
		t.Errorf("Expected: exported, saw: %s", itemOut.Other)
	}

	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestExportWithMultiTenancy(t *testing.T) {

	testCases := []struct {
		url        string
		objectMeta metav1.ObjectMeta
		selfLink   string
		namespace  string
		tenant     string
	}{
		{
			url:        "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id?export=true",
			objectMeta: metav1.ObjectMeta{Namespace: "other", Tenant: "else"},
			selfLink:   "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id",
			namespace:  "default",
			tenant:     "test",
		},
	}

	for _, testCase := range testCases {

		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{
			item: genericapitesting.Simple{
				ObjectMeta: testCase.objectMeta,
			},
		}
		selfLinker := &setTestSelfLinker{
			t:           t,
			expectedSet: testCase.selfLink,
			name:        "id",
			namespace:   testCase.namespace,
			tenant:      testCase.tenant,
		}
		storage["simple"] = &simpleStorage
		handler := handleLinker(storage, selfLinker)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testCase.url)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			data, _ := ioutil.ReadAll(resp.Body)
			resp.Body.Close()
			t.Fatalf("unexpected response: %#v\n%s\n", resp, string(data))
		}
		var itemOut genericapitesting.Simple
		body, err := extractBody(resp, &itemOut)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		if itemOut.Name != simpleStorage.item.Name {
			t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
		}

		if itemOut.Other != "exported" {
			t.Errorf("Expected: exported, saw: %s", itemOut.Other)
		}

		if !selfLinker.called {
			t.Errorf("Never set self link")
		}
	}
}

func TestGet(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
		name:        "id",
		namespace:   "default",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("unexpected response: %#v", resp)
	}
	var itemOut genericapitesting.Simple
	body, err := extractBody(resp, &itemOut)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	if itemOut.Name != simpleStorage.item.Name {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestGetWithMultiTenancy(t *testing.T) {

	testCases := []struct {
		url        string
		objectMeta metav1.ObjectMeta
		selfLink   string
		namespace  string
		tenant     string
	}{
		{
			url:        "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id",
			objectMeta: metav1.ObjectMeta{Namespace: "other", Tenant: "else"},
			selfLink:   "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id",
			namespace:  "default",
			tenant:     "test",
		},
	}

	for _, testCase := range testCases {

		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{
			item: genericapitesting.Simple{
				Other: "foo",
			},
		}
		selfLinker := &setTestSelfLinker{
			t:           t,
			expectedSet: testCase.selfLink,
			name:        "id",
			namespace:   testCase.namespace,
			tenant:      testCase.tenant,
		}
		storage["simple"] = &simpleStorage
		handler := handleLinker(storage, selfLinker)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testCase.url)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			data, _ := ioutil.ReadAll(resp.Body)
			resp.Body.Close()
			t.Fatalf("unexpected response: %#v\n%s\n", resp, string(data))
		}
		var itemOut genericapitesting.Simple
		body, err := extractBody(resp, &itemOut)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		if itemOut.Name != simpleStorage.item.Name {
			t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
		}

		if !selfLinker.called {
			t.Errorf("Never set self link")
		}
	}
}

func BenchmarkGet(b *testing.B) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
		name:        "id",
		namespace:   "default",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	u := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id"

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		resp, err := http.Get(u)
		if err != nil {
			b.Fatalf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			b.Fatalf("unexpected response: %#v", resp)
		}
		if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
			b.Fatalf("unable to read body")
		}
	}
	b.StopTimer()
}

func BenchmarkGetWithMultiTenancy(b *testing.B) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id",
		name:        "id",
		namespace:   "default",
		tenant:      "test",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	u := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id"

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		resp, err := http.Get(u)
		if err != nil {
			b.Fatalf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			b.Fatalf("unexpected response: %#v", resp)
		}
		if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
			b.Fatalf("unable to read body")
		}
	}
	b.StopTimer()
}

func BenchmarkGetNoCompression(b *testing.B) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
		name:        "id",
		namespace:   "default",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	client := &http.Client{
		Transport: &http.Transport{
			DisableCompression: true,
		},
	}

	u := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id"

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		resp, err := client.Get(u)
		if err != nil {
			b.Fatalf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			b.Fatalf("unexpected response: %#v", resp)
		}
		if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
			b.Fatalf("unable to read body")
		}
	}
	b.StopTimer()
}

func TestGetCompression(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: strings.Repeat("0123456789abcdef", (128*1024/16)+1),
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
		name:        "id",
		namespace:   "default",
	}

	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver())
	server := httptest.NewServer(handler)
	defer server.Close()

	tests := []struct {
		acceptEncoding string
	}{
		{acceptEncoding: ""},
		{acceptEncoding: "gzip"},
	}

	for _, test := range tests {
		req, err := http.NewRequest("GET", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", nil)
		if err != nil {
			t.Fatalf("unexpected error creating request: %v", err)
		}
		// It's necessary to manually set Accept-Encoding here
		// to prevent http.DefaultClient from automatically
		// decoding responses
		req.Header.Set("Accept-Encoding", test.acceptEncoding)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("unexpected response: %#v", resp)
		}
		var decoder *json.Decoder
		if test.acceptEncoding == "gzip" {
			gzipReader, err := gzip.NewReader(resp.Body)
			if err != nil {
				t.Fatalf("unexpected error creating gzip reader: %v", err)
			}
			decoder = json.NewDecoder(gzipReader)
		} else {
			decoder = json.NewDecoder(resp.Body)
		}
		var itemOut genericapitesting.Simple
		err = decoder.Decode(&itemOut)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Errorf("unexpected error reading body: %v", err)
		}

		if itemOut.Name != simpleStorage.item.Name {
			t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
		}
		if !selfLinker.called {
			t.Errorf("Never set self link")
		}
	}
}

func TestGetCompressionWithMultiTenancy(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: strings.Repeat("0123456789abcdef", (128*1024/16)+1),
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id",
		name:        "id",
		namespace:   "default",
		tenant:      "test",
	}

	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver())
	server := httptest.NewServer(handler)
	defer server.Close()

	tests := []struct {
		acceptEncoding string
	}{
		{acceptEncoding: ""},
		{acceptEncoding: "gzip"},
	}

	for _, test := range tests {
		req, err := http.NewRequest("GET", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/tenants/test/namespaces/default/simple/id", nil)
		if err != nil {
			t.Fatalf("unexpected error cretaing request: %v", err)
		}
		// It's necessary to manually set Accept-Encoding here
		// to prevent http.DefaultClient from automatically
		// decoding responses
		req.Header.Set("Accept-Encoding", test.acceptEncoding)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("unexpected response: %#v", resp)
		}
		var decoder *json.Decoder
		if test.acceptEncoding == "gzip" {
			gzipReader, err := gzip.NewReader(resp.Body)
			if err != nil {
				t.Fatalf("unexpected error creating gzip reader: %v", err)
			}
			decoder = json.NewDecoder(gzipReader)
		} else {
			decoder = json.NewDecoder(resp.Body)
		}
		var itemOut genericapitesting.Simple
		err = decoder.Decode(&itemOut)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Errorf("unexpected error reading body: %v", err)
		}

		if itemOut.Name != simpleStorage.item.Name {
			t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
		}
		if !selfLinker.called {
			t.Errorf("Never set self link")
		}
	}
}

func TestGetPretty(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
		name:        "id",
		namespace:   "default",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	tests := []struct {
		accept    string
		userAgent string
		params    url.Values
		pretty    bool
	}{
		{accept: runtime.ContentTypeJSON},
		{accept: "application/json;pretty=0"},
		{accept: runtime.ContentTypeJSON, userAgent: "kubectl"},
		{accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"0"}}},

		{pretty: true, accept: runtime.ContentTypeJSON, userAgent: "curl"},
		{pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Mozilla/5.0"},
		{pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Wget"},
		{pretty: true, accept: "application/json;pretty=1"},
		{pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"1"}}},
		{pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"true"}}},
	}
	for i, test := range tests {
		u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id")
		if err != nil {
			t.Fatal(err)
		}
		u.RawQuery = test.params.Encode()
		req := &http.Request{Method: "GET", URL: u}
		req.Header = http.Header{}
		req.Header.Set("Accept", test.accept)
		req.Header.Set("User-Agent", test.userAgent)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Fatal(err)
		}
		var itemOut genericapitesting.Simple
		body, err := extractBody(resp, &itemOut)
		if err != nil {
			t.Fatal(err)
		}
		// to get stable ordering we need to use a go type
		unstructured := genericapitesting.Simple{}
		if err := json.Unmarshal([]byte(body), &unstructured); err != nil {
			t.Fatal(err)
		}
		var expect string
		if test.pretty {
			out, err := json.MarshalIndent(unstructured, "", "  ")
			if err != nil {
				t.Fatal(err)
			}
			expect = string(out)
		} else {
			out, err := json.Marshal(unstructured)
			if err != nil {
				t.Fatal(err)
			}
			expect = string(out) + "\n"
		}
		if expect != body {
			t.Errorf("%d: body did not match expected:\n%s\n%s", i, body, expect)
		}
	}
}

func TestGetPrettyWithMultiTenancy(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id",
		name:        "id",
		namespace:   "default",
		tenant:      "test",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	tests := []struct {
		accept    string
		userAgent string
		params    url.Values
		pretty    bool
	}{
		{accept: runtime.ContentTypeJSON},
		{accept: "application/json;pretty=0"},
		{accept: runtime.ContentTypeJSON, userAgent: "kubectl"},
		{accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"0"}}},

		{pretty: true, accept: runtime.ContentTypeJSON, userAgent: "curl"},
		{pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Mozilla/5.0"},
		{pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Wget"},
		{pretty: true, accept: "application/json;pretty=1"},
		{pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"1"}}},
		{pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"true"}}},
	}
	for i, test := range tests {
		u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id")
		if err != nil {
			t.Fatal(err)
		}
		u.RawQuery = test.params.Encode()
		req := &http.Request{Method: "GET", URL: u}
		req.Header = http.Header{}
		req.Header.Set("Accept", test.accept)
		req.Header.Set("User-Agent", test.userAgent)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Fatal(err)
		}
		var itemOut genericapitesting.Simple
		body, err := extractBody(resp, &itemOut)
		if err != nil {
			t.Fatal(err)
		}
		// to get stable ordering we need to use a go type
		unstructured := genericapitesting.Simple{}
		if err := json.Unmarshal([]byte(body), &unstructured); err != nil {
			t.Fatal(err)
		}
		var expect string
		if test.pretty {
			out, err := json.MarshalIndent(unstructured, "", "  ")
			if err != nil {
				t.Fatal(err)
			}
			expect = string(out)
		} else {
			out, err := json.Marshal(unstructured)
			if err != nil {
				t.Fatal(err)
			}
			expect = string(out) + "\n"
		}
		if expect != body {
			t.Errorf("%d: body did not match expected:\n%s\n%s", i, body, expect)
		}
	}
}

func TestGetTable(t *testing.T) {
	now := metav1.Now()
	obj := genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", ResourceVersion: "10", SelfLink: "/blah", CreationTimestamp: now, UID: types.UID("abcdef0123")},
		Other:      "foo",
	}

	m, err := meta.Accessor(&obj)
	if err != nil {
		t.Fatal(err)
	}
	var encodedV1Beta1Body []byte
	{
		partial := meta.AsPartialObjectMetadata(m)
		partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
		encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion), partial)
		if err != nil {
			t.Fatal(err)
		}
		// the codec includes a trailing newline that is not present during decode
		encodedV1Beta1Body = bytes.TrimSpace(encodedBody)
	}
	var encodedV1Body []byte
	{
		partial := meta.AsPartialObjectMetadata(m)
		partial.GetObjectKind().SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
		encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1.SchemeGroupVersion), partial)
		if err != nil {
			t.Fatal(err)
		}
		// the codec includes a trailing newline that is not present during decode
		encodedV1Body = bytes.TrimSpace(encodedBody)
	}

	metaDoc := metav1.ObjectMeta{}.SwaggerDoc()

	tests := []struct {
		accept     string
		params     url.Values
		pretty     bool
		expected   *metav1beta1.Table
		statusCode int
		item       bool
	}{
		{
			accept:     "application/json;as=Table;v=v1alpha1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept:     runtime.ContentTypeProtobuf + ";as=Table;v=v1beta1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept:     runtime.ContentTypeProtobuf + ";as=Table;v=v1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},

		{
			item:   true,
			accept: "application/json;as=Table;v=v1;g=meta.k8s.io",
			expected: &metav1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
				ColumnDefinitions: []metav1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Body}},
				},
			},
		},
		{
			item:   true,
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			expected: &metav1beta1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1beta1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}},
				},
			},
		},
		{
			item: true,
			accept: strings.Join([]string{
				runtime.ContentTypeProtobuf + ";as=Table;v=v1beta1;g=meta.k8s.io",
				"application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			}, ","),
			expected: &metav1beta1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1beta1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}},
				},
			},
		},
		{
			item:   true,
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			params: url.Values{"includeObject": []string{"Metadata"}},
			expected: &metav1beta1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1beta1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}},
				},
			},
		},
		{
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			params: url.Values{"includeObject": []string{"Metadata"}},
			expected: &metav1beta1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/test/link"},
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1beta1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}},
				},
			},
		},
	}
	for i, test := range tests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			storage := map[string]rest.Storage{}
			simpleStorage := SimpleRESTStorage{
				item: obj,
				list: []genericapitesting.Simple{obj},
			}
			selfLinker := &setTestSelfLinker{
				t:           t,
				expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple",
				namespace:   "default",
			}
			if test.item {
				selfLinker.expectedSet += "/id"
				selfLinker.name = "id"
			}
			storage["simple"] = &simpleStorage
			handler := handleLinker(storage, selfLinker)
			server := httptest.NewServer(handler)
			defer server.Close()

			var id string
			if test.item {
				id = "/id"
			}
			u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple" + id)
			if err != nil {
				t.Fatal(err)
			}
			u.RawQuery = test.params.Encode()
			req := &http.Request{Method: "GET", URL: u}
			req.Header = http.Header{}
			req.Header.Set("Accept", test.accept)
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatal(err)
			}
			if test.statusCode != 0 {
				if resp.StatusCode != test.statusCode {
					t.Errorf("%d: unexpected response: %#v", i, resp)
				}
				obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme)
				if err != nil {
					t.Fatalf("%d: unexpected body read error: %v", i, err)
				}
				gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
				if obj.GetObjectKind().GroupVersionKind() != gvk {
					t.Fatalf("%d: unexpected error body: %#v", i, obj)
				}
				return
			}
			if resp.StatusCode != http.StatusOK {
				t.Errorf("%d: unexpected response: %#v", i, resp)
			}
			var itemOut metav1beta1.Table
			body, err := extractBody(resp, &itemOut)
			if err != nil {
				t.Fatal(err)
			}
			if !reflect.DeepEqual(test.expected, &itemOut) {
				t.Log(body)
				t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, &itemOut))
			}
		})
	}
}

func TestGetTableWithMultiTenancy(t *testing.T) {
	now := metav1.Now()
	obj := genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", Tenant: "te1", ResourceVersion: "10", SelfLink: "/blah", CreationTimestamp: now, UID: types.UID("abcdef0123")},
		Other:      "foo",
	}

	m, err := meta.Accessor(&obj)
	if err != nil {
		t.Fatal(err)
	}
	var encodedV1Beta1Body []byte
	{
		partial := meta.AsPartialObjectMetadata(m)
		partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
		encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion), partial)
		if err != nil {
			t.Fatal(err)
		}
		// the codec includes a trailing newline that is not present during decode
		encodedV1Beta1Body = bytes.TrimSpace(encodedBody)
	}
	var encodedV1Body []byte
	{
		partial := meta.AsPartialObjectMetadata(m)
		partial.GetObjectKind().SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
		encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1.SchemeGroupVersion), partial)
		if err != nil {
			t.Fatal(err)
		}
		// the codec includes a trailing newline that is not present during decode
		encodedV1Body = bytes.TrimSpace(encodedBody)
	}

	metaDoc := metav1.ObjectMeta{}.SwaggerDoc()

	tests := []struct {
		accept     string
		params     url.Values
		pretty     bool
		expected   *metav1beta1.Table
		statusCode int
		item       bool
	}{
		{
			accept:     "application/json;as=Table;v=v1alpha1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept:     runtime.ContentTypeProtobuf + ";as=Table;v=v1beta1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept:     runtime.ContentTypeProtobuf + ";as=Table;v=v1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},

		{
			item:   true,
			accept: "application/json;as=Table;v=v1;g=meta.k8s.io",
			expected: &metav1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
				ColumnDefinitions: []metav1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Body}},
				},
			},
		},
		{
			item:   true,
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			expected: &metav1beta1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1beta1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}},
				},
			},
		},
		{
			item: true,
			accept: strings.Join([]string{
				runtime.ContentTypeProtobuf + ";as=Table;v=v1beta1;g=meta.k8s.io",
				"application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			}, ","),
			expected: &metav1beta1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1beta1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}},
				},
			},
		},
		{
			item:   true,
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			params: url.Values{"includeObject": []string{"Metadata"}},
			expected: &metav1beta1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1beta1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}},
				},
			},
		},
		{
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			params: url.Values{"includeObject": []string{"Metadata"}},
			expected: &metav1beta1.Table{
				TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
				ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/test/link"},
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{
					{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
					{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
				},
				Rows: []metav1beta1.TableRow{
					{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}},
				},
			},
		},
	}
	for i, test := range tests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			storage := map[string]rest.Storage{}
			simpleStorage := SimpleRESTStorage{
				item: obj,
				list: []genericapitesting.Simple{obj},
			}
			selfLinker := &setTestSelfLinker{
				t:           t,
				expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple",
				tenant:      "test",
				namespace:   "default",
			}
			if test.item {
				selfLinker.expectedSet += "/id"
				selfLinker.name = "id"
			}
			storage["simple"] = &simpleStorage
			handler := handleLinker(storage, selfLinker)
			server := httptest.NewServer(handler)
			defer server.Close()

			var id string
			if test.item {
				id = "/id"
			}
			u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple" + id)
			if err != nil {
				t.Fatal(err)
			}
			u.RawQuery = test.params.Encode()
			req := &http.Request{Method: "GET", URL: u}
			req.Header = http.Header{}
			req.Header.Set("Accept", test.accept)
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatal(err)
			}
			if test.statusCode != 0 {
				if resp.StatusCode != test.statusCode {
					t.Errorf("%d: unexpected response: %#v", i, resp)
				}
				obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme)
				if err != nil {
					t.Fatalf("%d: unexpected body read error: %v", i, err)
				}
				gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
				if obj.GetObjectKind().GroupVersionKind() != gvk {
					t.Fatalf("%d: unexpected error body: %#v", i, obj)
				}
				return
			}
			if resp.StatusCode != http.StatusOK {
				t.Errorf("%d: unexpected response: %#v", i, resp)
			}
			var itemOut metav1beta1.Table
			body, err := extractBody(resp, &itemOut)
			if err != nil {
				t.Fatal(err)
			}
			if !reflect.DeepEqual(test.expected, &itemOut) {
				t.Log(body)
				t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, &itemOut))
			}
		})
	}
}

func TestWatchTable(t *testing.T) {
	obj := genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", ResourceVersion: "10", SelfLink: "/blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0)), UID: types.UID("abcdef0123")},
		Other:      "foo",
	}

	m, err := meta.Accessor(&obj)
	if err != nil {
		t.Fatal(err)
	}
	partial := meta.AsPartialObjectMetadata(m)
	partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
	encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion), partial)
	if err != nil {
		t.Fatal(err)
	}
	// the codec includes a trailing newline that is not present during decode
	encodedBody = bytes.TrimSpace(encodedBody)

	encodedBodyV1, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1.SchemeGroupVersion), partial)
	if err != nil {
		t.Fatal(err)
	}
	// the codec includes a trailing newline that is not present during decode
	encodedBodyV1 = bytes.TrimSpace(encodedBodyV1)

	metaDoc := metav1.ObjectMeta{}.SwaggerDoc()

	s := metainternalversion.Codecs.SupportedMediaTypes()[0].Serializer

	tests := []struct {
		accept string
		params url.Values
		send   func(w *watch.FakeWatcher)

		expected    []*metav1.WatchEvent
		contentType string
		statusCode  int
		item        bool
	}{
		{
			accept:     "application/json;as=Table;v=v1alpha1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			send: func(w *watch.FakeWatcher) {
				w.Add(&obj)
			},
			expected: []*metav1.WatchEvent{
				{
					Type: "ADDED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1beta1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							ColumnDefinitions: []metav1beta1.TableColumnDefinition{
								{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
								{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
							},
							Rows: []metav1beta1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}},
							},
						}))),
					},
				},
			},
		},
		{
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			send: func(w *watch.FakeWatcher) {
				w.Add(&obj)
				w.Modify(&obj)
			},
			expected: []*metav1.WatchEvent{
				{
					Type: "ADDED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1beta1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							ColumnDefinitions: []metav1beta1.TableColumnDefinition{
								{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
								{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
							},
							Rows: []metav1beta1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}},
							},
						}))),
					},
				},
				{
					Type: "MODIFIED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1beta1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							Rows: []metav1beta1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}},
							},
						}))),
					},
				},
			},
		},
		{
			accept: "application/json;as=Table;v=v1;g=meta.k8s.io",
			send: func(w *watch.FakeWatcher) {
				w.Add(&obj)
				w.Modify(&obj)
			},
			expected: []*metav1.WatchEvent{
				{
					Type: "ADDED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							ColumnDefinitions: []metav1beta1.TableColumnDefinition{
								{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
								{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
							},
							Rows: []metav1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBodyV1}},
							},
						}))),
					},
				},
				{
					Type: "MODIFIED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							Rows: []metav1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBodyV1}},
							},
						}))),
					},
				},
			},
		},
	}
	for i, test := range tests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			storage := map[string]rest.Storage{}
			simpleStorage := SimpleRESTStorage{
				item: obj,
				list: []genericapitesting.Simple{obj},
			}

			selfLinker := &setTestSelfLinker{
				t:           t,
				expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple",
				namespace:   "default",
			}
			if test.item {
				selfLinker.expectedSet += "/id"
				selfLinker.name = "id"
			}
			storage["simple"] = &simpleStorage
			handler := handleLinker(storage, selfLinker)
			server := httptest.NewServer(handler)
			defer server.Close()

			var id string
			if test.item {
				id = "/id"
			}
			u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple")
			if err != nil {
				t.Fatal(err)
			}
			if test.params == nil {
				test.params = url.Values{}
			}
			if test.item {
				test.params["fieldSelector"] = []string{fmt.Sprintf("metadata.name=%s", id)}
			}
			test.params["watch"] = []string{"1"}

			u.RawQuery = test.params.Encode()
			req := &http.Request{Method: "GET", URL: u}
			req.Header = http.Header{}
			req.Header.Set("Accept", test.accept)
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatal(err)
			}
			defer resp.Body.Close()
			if test.statusCode != 0 {
				if resp.StatusCode != test.statusCode {
					t.Fatalf("%d: unexpected response: %#v", i, resp)
				}
				obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme)
				if err != nil {
					t.Fatalf("%d: unexpected body read error: %v", i, err)
				}
				gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
				if obj.GetObjectKind().GroupVersionKind() != gvk {
					t.Fatalf("%d: unexpected error body: %#v", i, obj)
				}
				return
			}
			if resp.StatusCode != http.StatusOK {
				t.Fatalf("%d: unexpected response: %#v", i, resp)
			}

			go func() {
				defer simpleStorage.fakeWatch.Stop()
				test.send(simpleStorage.fakeWatch)
			}()

			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("Body:\n%s", string(body))
			d := watcher(resp.Header.Get("Content-Type"), ioutil.NopCloser(bytes.NewReader(body)))
			var actual []*metav1.WatchEvent
			for {
				var event metav1.WatchEvent
				_, _, err := d.Decode(nil, &event)
				if err == io.EOF {
					break
				}
				if err != nil {
					t.Fatal(err)
				}
				actual = append(actual, &event)
			}
			if !reflect.DeepEqual(test.expected, actual) {
				for i := range test.expected {
					if i >= len(actual) {
						break
					}
					t.Logf("%s", diff.StringDiff(string(test.expected[i].Object.Raw), string(actual[i].Object.Raw)))
				}
				t.Fatalf("unexpected: %s", diff.ObjectReflectDiff(test.expected, actual))
			}
		})
	}
}

func TestWatchTableWithMultiTenancy(t *testing.T) {
	obj := genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", Tenant: "te1", ResourceVersion: "10", SelfLink: "/blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0)), UID: types.UID("abcdef0123")},
		Other:      "foo",
	}

	m, err := meta.Accessor(&obj)
	if err != nil {
		t.Fatal(err)
	}
	partial := meta.AsPartialObjectMetadata(m)
	partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
	encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion), partial)
	if err != nil {
		t.Fatal(err)
	}
	// the codec includes a trailing newline that is not present during decode
	encodedBody = bytes.TrimSpace(encodedBody)

	encodedBodyV1, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1.SchemeGroupVersion), partial)
	if err != nil {
		t.Fatal(err)
	}
	// the codec includes a trailing newline that is not present during decode
	encodedBodyV1 = bytes.TrimSpace(encodedBodyV1)

	metaDoc := metav1.ObjectMeta{}.SwaggerDoc()

	s := metainternalversion.Codecs.SupportedMediaTypes()[0].Serializer

	tests := []struct {
		accept string
		params url.Values
		send   func(w *watch.FakeWatcher)

		expected    []*metav1.WatchEvent
		contentType string
		statusCode  int
		item        bool
	}{
		{
			accept:     "application/json;as=Table;v=v1alpha1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			send: func(w *watch.FakeWatcher) {
				w.Add(&obj)
			},
			expected: []*metav1.WatchEvent{
				{
					Type: "ADDED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1beta1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							ColumnDefinitions: []metav1beta1.TableColumnDefinition{
								{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
								{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
							},
							Rows: []metav1beta1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}},
							},
						}))),
					},
				},
			},
		},
		{
			accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io",
			send: func(w *watch.FakeWatcher) {
				w.Add(&obj)
				w.Modify(&obj)
			},
			expected: []*metav1.WatchEvent{
				{
					Type: "ADDED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1beta1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							ColumnDefinitions: []metav1beta1.TableColumnDefinition{
								{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
								{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
							},
							Rows: []metav1beta1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}},
							},
						}))),
					},
				},
				{
					Type: "MODIFIED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1beta1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							Rows: []metav1beta1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}},
							},
						}))),
					},
				},
			},
		},
		{
			accept: "application/json;as=Table;v=v1;g=meta.k8s.io",
			send: func(w *watch.FakeWatcher) {
				w.Add(&obj)
				w.Modify(&obj)
			},
			expected: []*metav1.WatchEvent{
				{
					Type: "ADDED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							ColumnDefinitions: []metav1beta1.TableColumnDefinition{
								{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
								{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
							},
							Rows: []metav1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBodyV1}},
							},
						}))),
					},
				},
				{
					Type: "MODIFIED",
					Object: runtime.RawExtension{
						Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{
							TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"},
							ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
							Rows: []metav1.TableRow{
								{Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBodyV1}},
							},
						}))),
					},
				},
			},
		},
	}
	for i, test := range tests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			storage := map[string]rest.Storage{}
			simpleStorage := SimpleRESTStorage{
				item: obj,
				list: []genericapitesting.Simple{obj},
			}

			selfLinker := &setTestSelfLinker{
				t:           t,
				expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/system/namespaces/default/simple",
				namespace:   "default",
				tenant:      metav1.TenantSystem,
			}
			if test.item {
				selfLinker.expectedSet += "/id"
				selfLinker.name = "id"
			}
			storage["simple"] = &simpleStorage
			handler := handleLinker(storage, selfLinker)
			server := httptest.NewServer(handler)
			defer server.Close()

			var id string
			if test.item {
				id = "/id"
			}
			u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/system/namespaces/default/simple")
			if err != nil {
				t.Fatal(err)
			}
			if test.params == nil {
				test.params = url.Values{}
			}
			if test.item {
				test.params["fieldSelector"] = []string{fmt.Sprintf("metadata.name=%s", id)}
			}
			test.params["watch"] = []string{"1"}

			u.RawQuery = test.params.Encode()
			req := &http.Request{Method: "GET", URL: u}
			req.Header = http.Header{}
			req.Header.Set("Accept", test.accept)
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatal(err)
			}
			defer resp.Body.Close()
			if test.statusCode != 0 {
				if resp.StatusCode != test.statusCode {
					t.Fatalf("%d: unexpected response: %#v", i, resp)
				}
				obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme)
				if err != nil {
					t.Fatalf("%d: unexpected body read error: %v", i, err)
				}
				gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
				if obj.GetObjectKind().GroupVersionKind() != gvk {
					t.Fatalf("%d: unexpected error body: %#v", i, obj)
				}
				return
			}
			if resp.StatusCode != http.StatusOK {
				t.Fatalf("%d: unexpected response: %#v", i, resp)
			}

			go func() {
				defer simpleStorage.fakeWatch.Stop()
				test.send(simpleStorage.fakeWatch)
			}()

			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("Body:\n%s", string(body))
			d := watcher(resp.Header.Get("Content-Type"), ioutil.NopCloser(bytes.NewReader(body)))
			var actual []*metav1.WatchEvent
			for {
				var event metav1.WatchEvent
				_, _, err := d.Decode(nil, &event)
				if err == io.EOF {
					break
				}
				if err != nil {
					t.Fatal(err)
				}
				actual = append(actual, &event)
			}
			if !reflect.DeepEqual(test.expected, actual) {
				for i := range test.expected {
					if i >= len(actual) {
						break
					}
					t.Logf("%s", diff.StringDiff(string(test.expected[i].Object.Raw), string(actual[i].Object.Raw)))
				}
				t.Fatalf("unexpected: %s", diff.ObjectReflectDiff(test.expected, actual))
			}
		})
	}
}

func watcher(mediaType string, r io.ReadCloser) streaming.Decoder {
	info, ok := runtime.SerializerInfoForMediaType(metainternalversion.Codecs.SupportedMediaTypes(), mediaType)
	if !ok || info.StreamSerializer == nil {
		panic(info)
	}
	streamSerializer := info.StreamSerializer
	fr := streamSerializer.Framer.NewFrameReader(r)
	d := streaming.NewDecoder(fr, streamSerializer.Serializer)
	return d
}

func TestGetPartialObjectMetadata(t *testing.T) {
	now := metav1.Time{metav1.Now().Rfc3339Copy().Local()}
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")},
			Other:      "foo",
		},
		list: []genericapitesting.Simple{
			{
				ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("newer")},
				Other:      "foo",
			},
			{
				ObjectMeta: metav1.ObjectMeta{Name: "foo2", Namespace: "ns2", CreationTimestamp: now, UID: types.UID("older")},
				Other:      "bar",
			},
		},
	}
	selfLinker := &setTestSelfLinker{
		t:              t,
		expectedSet:    "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
		alternativeSet: sets.NewString("/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple"),
		name:           "id",
		namespace:      "default",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	tests := []struct {
		accept     string
		params     url.Values
		pretty     bool
		list       bool
		expected   runtime.Object
		expectKind schema.GroupVersionKind
		statusCode int
	}{
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "Simple", Group: testGroupVersion.Group, Version: testGroupVersion.Version},
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			list:       true,
			accept:     "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},

		// verify preferred version overrides supported version
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"},
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"},
		},

		{
			list:       true,
			accept:     "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "SimpleList", Group: testGroupVersion.Group, Version: testGroupVersion.Version},
		},
		{
			list:       true,
			accept:     "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			accept:     "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io",
			expected: &metav1beta1.PartialObjectMetadata{
				ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")},
			},
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			accept: "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io",
			expected: &metav1.PartialObjectMetadata{
				ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")},
			},
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"},
		},
		{
			list:   true,
			accept: "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io",
			expected: &metav1beta1.PartialObjectMetadataList{
				ListMeta: metav1.ListMeta{
					ResourceVersion: "10",
					SelfLink:        "/test/link",
				},
				Items: []metav1beta1.PartialObjectMetadata{
					{
						TypeMeta:   metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "PartialObjectMetadata"},
						ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("newer")},
					},
					{
						TypeMeta:   metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "PartialObjectMetadata"},
						ObjectMeta: metav1.ObjectMeta{Name: "foo2", Namespace: "ns2", CreationTimestamp: now, UID: types.UID("older")},
					},
				},
			},
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1beta1"},
		},
	}
	for i, test := range tests {
		suffix := "/namespaces/default/simple/id"
		if test.list {
			suffix = "/namespaces/default/simple"
		}
		u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + suffix)
		if err != nil {
			t.Fatal(err)
		}
		u.RawQuery = test.params.Encode()
		req := &http.Request{Method: "GET", URL: u}
		req.Header = http.Header{}
		req.Header.Set("Accept", test.accept)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		if test.statusCode != 0 {
			if resp.StatusCode != test.statusCode {
				t.Errorf("%d: unexpected response: %#v", i, resp)
			}
			obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme)
			if err != nil {
				t.Errorf("%d: unexpected body read error: %v", i, err)
				continue
			}
			gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
			if obj.GetObjectKind().GroupVersionKind() != gvk {
				t.Errorf("%d: unexpected error body: %#v", i, obj)
			}
			continue
		}
		if resp.StatusCode != http.StatusOK {
			t.Errorf("%d: invalid status: %#v\n%s", i, resp, bodyOrDie(resp))
			continue
		}
		body := ""
		if test.expected != nil {
			itemOut, d, err := extractBodyObject(resp, metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion))
			if err != nil {
				t.Fatal(err)
			}
			if !reflect.DeepEqual(test.expected, itemOut) {
				t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, itemOut))
			}
			body = d
		} else {
			d, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Fatal(err)
			}
			body = string(d)
		}
		obj := &unstructured.Unstructured{}
		if err := json.Unmarshal([]byte(body), obj); err != nil {
			t.Fatal(err)
		}
		if obj.GetObjectKind().GroupVersionKind() != test.expectKind {
			t.Errorf("%d: unexpected kind: %#v", i, obj.GetObjectKind().GroupVersionKind())
		}
	}
}

func TestGetPartialObjectMetadataWithMultiTenancy(t *testing.T) {
	now := metav1.Time{metav1.Now().Rfc3339Copy().Local()}
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", Tenant: "te1", CreationTimestamp: now, UID: types.UID("abcdef0123")},
			Other:      "foo",
		},
		list: []genericapitesting.Simple{
			{
				ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", Tenant: "te1", CreationTimestamp: now, UID: types.UID("newer")},
				Other:      "foo",
			},
			{
				ObjectMeta: metav1.ObjectMeta{Name: "foo2", Namespace: "ns2", Tenant: "te2", CreationTimestamp: now, UID: types.UID("older")},
				Other:      "bar",
			},
		},
	}
	selfLinker := &setTestSelfLinker{
		t:              t,
		expectedSet:    "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id",
		alternativeSet: sets.NewString("/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple"),
		name:           "id",
		namespace:      "default",
		tenant:         "test",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	tests := []struct {
		accept     string
		params     url.Values
		pretty     bool
		list       bool
		expected   runtime.Object
		expectKind schema.GroupVersionKind
		statusCode int
	}{
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "Simple", Group: testGroupVersion.Group, Version: testGroupVersion.Version},
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			list:       true,
			accept:     "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},

		// verify preferred version overrides supported version
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"},
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			accept:     "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"},
		},

		{
			list:       true,
			accept:     "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "SimpleList", Group: testGroupVersion.Group, Version: testGroupVersion.Version},
		},
		{
			list:       true,
			accept:     "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io, application/json",
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			accept:     "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io",
			statusCode: http.StatusNotAcceptable,
		},
		{
			accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io",
			expected: &metav1beta1.PartialObjectMetadata{
				ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", Tenant: "te1", CreationTimestamp: now, UID: types.UID("abcdef0123")},
			},
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"},
		},
		{
			accept: "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io",
			expected: &metav1.PartialObjectMetadata{
				ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", Tenant: "te1", CreationTimestamp: now, UID: types.UID("abcdef0123")},
			},
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"},
		},
		{
			list:   true,
			accept: "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io",
			expected: &metav1beta1.PartialObjectMetadataList{
				ListMeta: metav1.ListMeta{
					ResourceVersion: "10",
					SelfLink:        "/test/link",
				},
				Items: []metav1beta1.PartialObjectMetadata{
					{
						TypeMeta:   metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "PartialObjectMetadata"},
						ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", Tenant: "te1", CreationTimestamp: now, UID: types.UID("newer")},
					},
					{
						TypeMeta:   metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "PartialObjectMetadata"},
						ObjectMeta: metav1.ObjectMeta{Name: "foo2", Namespace: "ns2", Tenant: "te2", CreationTimestamp: now, UID: types.UID("older")},
					},
				},
			},
			expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1beta1"},
		},
	}
	for i, test := range tests {
		suffix := "/tenants/test/namespaces/default/simple/id"
		if test.list {
			suffix = "/tenants/test/namespaces/default/simple"
		}
		u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + suffix)
		if err != nil {
			t.Fatal(err)
		}
		u.RawQuery = test.params.Encode()
		req := &http.Request{Method: "GET", URL: u}
		req.Header = http.Header{}
		req.Header.Set("Accept", test.accept)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		if test.statusCode != 0 {
			if resp.StatusCode != test.statusCode {
				t.Errorf("%d: unexpected response: %#v", i, resp)
			}
			obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme)
			if err != nil {
				t.Errorf("%d: unexpected body read error: %v", i, err)
				continue
			}
			gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
			if obj.GetObjectKind().GroupVersionKind() != gvk {
				t.Errorf("%d: unexpected error body: %#v", i, obj)
			}
			continue
		}
		if resp.StatusCode != http.StatusOK {
			t.Errorf("%d: invalid status: %#v\n%s", i, resp, bodyOrDie(resp))
			continue
		}
		body := ""
		if test.expected != nil {
			itemOut, d, err := extractBodyObject(resp, metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion))
			if err != nil {
				t.Fatal(err)
			}
			if !reflect.DeepEqual(test.expected, itemOut) {
				t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, itemOut))
			}
			body = d
		} else {
			d, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Fatal(err)
			}
			body = string(d)
		}
		obj := &unstructured.Unstructured{}
		if err := json.Unmarshal([]byte(body), obj); err != nil {
			t.Fatal(err)
		}
		if obj.GetObjectKind().GroupVersionKind() != test.expectKind {
			t.Errorf("%d: unexpected kind: %#v", i, obj.GetObjectKind().GroupVersionKind())
		}
	}
}

func TestGetBinary(t *testing.T) {
	simpleStorage := SimpleRESTStorage{
		stream: &SimpleStream{
			contentType: "text/plain",
			Reader:      bytes.NewBufferString("response data"),
		},
	}
	stream := simpleStorage.stream
	server := httptest.NewServer(handle(map[string]rest.Storage{"simple": &simpleStorage}))
	defer server.Close()

	req, err := http.NewRequest("GET", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/binary", nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	req.Header.Add("Accept", "text/other, */*")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("unexpected response: %#v", resp)
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if !stream.closed || stream.version != testGroupVersion.String() || stream.accept != "text/other, */*" ||
		resp.Header.Get("Content-Type") != stream.contentType || string(body) != "response data" {
		t.Errorf("unexpected stream: %#v", stream)
	}
}

func TestGetBinaryWithMultiTenancy(t *testing.T) {
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/binary",
	}

	for _, testUrl := range testUrls {
		simpleStorage := SimpleRESTStorage{
			stream: &SimpleStream{
				contentType: "text/plain",
				Reader:      bytes.NewBufferString("response data"),
			},
		}
		stream := simpleStorage.stream
		server := httptest.NewServer(handle(map[string]rest.Storage{"simple": &simpleStorage}))
		defer server.Close()

		req, err := http.NewRequest("GET", server.URL+testUrl, nil)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		req.Header.Add("Accept", "text/other, */*")
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("unexpected response: %#v", resp)
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if !stream.closed || stream.version != testGroupVersion.String() || stream.accept != "text/other, */*" ||
			resp.Header.Get("Content-Type") != stream.contentType || string(body) != "response data" {
			t.Errorf("unexpected stream: %#v", stream)
		}
	}
}

func validateSimpleGetOptionsParams(t *testing.T, route *restful.Route) {
	// Validate name and description
	expectedParams := map[string]string{
		"param1":  "description for param1",
		"param2":  "description for param2",
		"atAPath": "",
	}
	for _, p := range route.ParameterDocs {
		data := p.Data()
		if desc, exists := expectedParams[data.Name]; exists {
			if desc != data.Description {
				t.Errorf("unexpected description for parameter %s: %s\n", data.Name, data.Description)
			}
			delete(expectedParams, data.Name)
		}
	}
	if len(expectedParams) > 0 {
		t.Errorf("did not find all expected parameters: %#v", expectedParams)
	}
}

func TestGetWithOptionsRouteParams(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := GetWithOptionsRESTStorage{
		SimpleRESTStorage: &SimpleRESTStorage{},
	}
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	ws := handler.(*defaultAPIServer).container.RegisteredWebServices()
	if len(ws) == 0 {
		t.Fatal("no web services registered")
	}
	routes := ws[0].Routes()
	for i := range routes {
		if routes[i].Method == "GET" && routes[i].Operation == "readNamespacedSimple" {
			validateSimpleGetOptionsParams(t, &routes[i])
			break
		}
	}
}

func TestGetWithOptions(t *testing.T) {

	tests := []struct {
		name         string
		rootScoped   bool
		requestURL   string
		expectedPath string
	}{
		{
			name:         "basic",
			requestURL:   "/namespaces/default/simple/id?param1=test1&param2=test2",
			expectedPath: "",
		},
		{
			name:         "with root slash",
			requestURL:   "/namespaces/default/simple/id/?param1=test1&param2=test2",
			expectedPath: "/",
		},
		{
			name:         "with path",
			requestURL:   "/namespaces/default/simple/id/a/different/path?param1=test1&param2=test2",
			expectedPath: "/a/different/path",
		},
		{
			name:         "with path with trailing slash",
			requestURL:   "/namespaces/default/simple/id/a/different/path/?param1=test1&param2=test2",
			expectedPath: "/a/different/path/",
		},
		{
			name:         "as subresource",
			requestURL:   "/namespaces/default/simple/id/subresource/another/different/path?param1=test1&param2=test2",
			expectedPath: "/another/different/path",
		},
		{
			name:         "cluster-scoped basic",
			rootScoped:   true,
			requestURL:   "/simple/id?param1=test1&param2=test2",
			expectedPath: "",
		},
		{
			name:         "cluster-scoped basic with path",
			rootScoped:   true,
			requestURL:   "/simple/id/a/cluster/path?param1=test1&param2=test2",
			expectedPath: "/a/cluster/path",
		},
		{
			name:         "cluster-scoped basic as subresource",
			rootScoped:   true,
			requestURL:   "/simple/id/subresource/another/cluster/path?param1=test1&param2=test2",
			expectedPath: "/another/cluster/path",
		},
	}

	for _, test := range tests {
		simpleStorage := GetWithOptionsRESTStorage{
			SimpleRESTStorage: &SimpleRESTStorage{
				item: genericapitesting.Simple{
					Other: "foo",
				},
			},
			takesPath: "atAPath",
		}
		simpleRootStorage := GetWithOptionsRootRESTStorage{
			SimpleTypedStorage: &SimpleTypedStorage{
				baseType: &genericapitesting.SimpleRoot{}, // a root scoped type
				item: &genericapitesting.SimpleRoot{
					Other: "foo",
				},
			},
			takesPath: "atAPath",
		}

		storage := map[string]rest.Storage{}
		if test.rootScoped {
			storage["simple"] = &simpleRootStorage
			storage["simple/subresource"] = &simpleRootStorage
		} else {
			storage["simple"] = &simpleStorage
			storage["simple/subresource"] = &simpleStorage
		}
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + test.requestURL)
		if err != nil {
			t.Errorf("%s: %v", test.name, err)
			continue
		}
		if resp.StatusCode != http.StatusOK {
			t.Errorf("%s: unexpected response: %#v", test.name, resp)
			continue
		}
		var itemOut genericapitesting.Simple
		body, err := extractBody(resp, &itemOut)
		if err != nil {
			t.Errorf("%s: %v", test.name, err)
			continue
		}

		if itemOut.Name != simpleStorage.item.Name {
			t.Errorf("%s: Unexpected data: %#v, expected %#v (%s)", test.name, itemOut, simpleStorage.item, string(body))
			continue
		}

		var opts *genericapitesting.SimpleGetOptions
		var ok bool
		if test.rootScoped {
			opts, ok = simpleRootStorage.optionsReceived.(*genericapitesting.SimpleGetOptions)
		} else {
			opts, ok = simpleStorage.optionsReceived.(*genericapitesting.SimpleGetOptions)

		}
		if !ok {
			t.Errorf("%s: Unexpected options object received: %#v", test.name, simpleStorage.optionsReceived)
			continue
		}
		if opts.Param1 != "test1" || opts.Param2 != "test2" {
			t.Errorf("%s: Did not receive expected options: %#v", test.name, opts)
			continue
		}
		if opts.Path != test.expectedPath {
			t.Errorf("%s: Unexpected path value. Expected: %s. Actual: %s.", test.name, test.expectedPath, opts.Path)
			continue
		}
	}
}

func TestGetWithOptionsWithMultiTenancy(t *testing.T) {

	tests := []struct {
		name         string
		rootScoped   bool
		requestURL   string
		expectedPath string
	}{
		{
			name:         "basic",
			requestURL:   "/tenants/test/namespaces/default/simple/id?param1=test1&param2=test2",
			expectedPath: "",
		},
		{
			name:         "with root slash",
			requestURL:   "/tenants/test/namespaces/default/simple/id/?param1=test1&param2=test2",
			expectedPath: "/",
		},
		{
			name:         "with path",
			requestURL:   "/tenants/test/namespaces/default/simple/id/a/different/path?param1=test1&param2=test2",
			expectedPath: "/a/different/path",
		},
		{
			name:         "with path with trailing slash",
			requestURL:   "/tenants/test/namespaces/default/simple/id/a/different/path/?param1=test1&param2=test2",
			expectedPath: "/a/different/path/",
		},
		{
			name:         "as subresource",
			requestURL:   "/tenants/test/namespaces/default/simple/id/subresource/another/different/path?param1=test1&param2=test2",
			expectedPath: "/another/different/path",
		},
		{
			name:         "cluster-scoped basic",
			rootScoped:   true,
			requestURL:   "/simple/id?param1=test1&param2=test2",
			expectedPath: "",
		},
		{
			name:         "cluster-scoped basic with path",
			rootScoped:   true,
			requestURL:   "/simple/id/a/cluster/path?param1=test1&param2=test2",
			expectedPath: "/a/cluster/path",
		},
		{
			name:         "cluster-scoped basic as subresource",
			rootScoped:   true,
			requestURL:   "/simple/id/subresource/another/cluster/path?param1=test1&param2=test2",
			expectedPath: "/another/cluster/path",
		},
	}

	for _, test := range tests {
		simpleStorage := GetWithOptionsRESTStorage{
			SimpleRESTStorage: &SimpleRESTStorage{
				item: genericapitesting.Simple{
					Other: "foo",
				},
			},
			takesPath: "atAPath",
		}
		simpleRootStorage := GetWithOptionsRootRESTStorage{
			SimpleTypedStorage: &SimpleTypedStorage{
				baseType: &genericapitesting.SimpleRoot{}, // a root scoped type
				item: &genericapitesting.SimpleRoot{
					Other: "foo",
				},
			},
			takesPath: "atAPath",
		}

		storage := map[string]rest.Storage{}
		if test.rootScoped {
			storage["simple"] = &simpleRootStorage
			storage["simple/subresource"] = &simpleRootStorage
		} else {
			storage["simple"] = &simpleStorage
			storage["simple/subresource"] = &simpleStorage
		}
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + test.requestURL)
		if err != nil {
			t.Errorf("%s: %v", test.name, err)
			continue
		}
		if resp.StatusCode != http.StatusOK {
			t.Errorf("%s: unexpected response: %#v", test.name, resp)
			continue
		}
		var itemOut genericapitesting.Simple
		body, err := extractBody(resp, &itemOut)
		if err != nil {
			t.Errorf("%s: %v", test.name, err)
			continue
		}

		if itemOut.Name != simpleStorage.item.Name {
			t.Errorf("%s: Unexpected data: %#v, expected %#v (%s)", test.name, itemOut, simpleStorage.item, string(body))
			continue
		}

		var opts *genericapitesting.SimpleGetOptions
		var ok bool
		if test.rootScoped {
			opts, ok = simpleRootStorage.optionsReceived.(*genericapitesting.SimpleGetOptions)
		} else {
			opts, ok = simpleStorage.optionsReceived.(*genericapitesting.SimpleGetOptions)

		}
		if !ok {
			t.Errorf("%s: Unexpected options object received: %#v", test.name, simpleStorage.optionsReceived)
			continue
		}
		if opts.Param1 != "test1" || opts.Param2 != "test2" {
			t.Errorf("%s: Did not receive expected options: %#v", test.name, opts)
			continue
		}
		if opts.Path != test.expectedPath {
			t.Errorf("%s: Unexpected path value. Expected: %s. Actual: %s.", test.name, test.expectedPath, opts.Path)
			continue
		}
	}
}

func TestGetAlternateSelfLink(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/test/simple/id",
		name:        "id",
		namespace:   "test",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/test/simple/id")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("unexpected response: %#v", resp)
	}
	var itemOut genericapitesting.Simple
	body, err := extractBody(resp, &itemOut)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if itemOut.Name != simpleStorage.item.Name {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestGetAlternateSelfLinkWithMultiTenancy(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/test/simple/id",
		name:        "id",
		namespace:   "test",
		tenant:      "test",
	}
	storage["simple"] = &simpleStorage
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/test/simple/id")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("unexpected response: %#v", resp)
	}
	var itemOut genericapitesting.Simple
	body, err := extractBody(resp, &itemOut)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if itemOut.Name != simpleStorage.item.Name {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestGetNamespaceSelfLink(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simple/id",
		name:        "id",
		namespace:   "foo",
	}
	storage["simple"] = &simpleStorage
	handler := handleInternal(storage, admissionControl, selfLinker, nil)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simple/id")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("unexpected response: %#v", resp)
	}
	var itemOut genericapitesting.Simple
	body, err := extractBody(resp, &itemOut)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if itemOut.Name != simpleStorage.item.Name {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestGetNamespaceSelfLinkWithMultiTenancy(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		item: genericapitesting.Simple{
			Other: "foo",
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/namespaces/foo/simple/id",
		name:        "id",
		namespace:   "foo",
		tenant:      "test",
	}
	storage["simple"] = &simpleStorage
	handler := handleInternal(storage, admissionControl, selfLinker, nil)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/namespaces/foo/simple/id")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("unexpected response: %#v", resp)
	}
	var itemOut genericapitesting.Simple
	body, err := extractBody(resp, &itemOut)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if itemOut.Name != simpleStorage.item.Name {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestGetMissing(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		errors: map[string]error{"get": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, "id")},
	}
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id")
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	if resp.StatusCode != http.StatusNotFound {
		t.Errorf("Unexpected response %#v", resp)
	}
}

func TestGetMissingWithMultiTenancy(t *testing.T) {
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id",
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{
			errors: map[string]error{"get": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, "id")},
		}
		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testUrl)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		if resp.StatusCode != http.StatusNotFound {
			t.Errorf("Unexpected response %#v", resp)
		}
	}
}

func TestGetRetryAfter(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{
		errors: map[string]error{"get": apierrs.NewServerTimeout(schema.GroupResource{Resource: "simples"}, "id", 2)},
	}
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id")
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusInternalServerError {
		t.Errorf("Unexpected response %#v", resp)
	}
	if resp.Header.Get("Retry-After") != "2" {
		t.Errorf("Unexpected Retry-After header: %v", resp.Header)
	}
}

func TestGetRetryAfterWithMultiTenancy(t *testing.T) {
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/id",
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{
			errors: map[string]error{"get": apierrs.NewServerTimeout(schema.GroupResource{Resource: "simples"}, "id", 2)},
		}
		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testUrl)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusInternalServerError {
			t.Errorf("Unexpected response %#v", resp)
		}
		if resp.Header.Get("Retry-After") != "2" {
			t.Errorf("Unexpected Retry-After header: %v", resp.Header)
		}
	}
}

func TestConnect(t *testing.T) {
	responseText := "Hello World"
	itemID := "theID"
	connectStorage := &ConnecterRESTStorage{
		connectHandler: &OutputConnect{
			response: responseText,
		},
	}
	storage := map[string]rest.Storage{
		"simple":         &SimpleRESTStorage{},
		"simple/connect": connectStorage,
	}
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect")

	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Errorf("unexpected response: %#v", resp)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if connectStorage.receivedID != itemID {
		t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
	}
	if string(body) != responseText {
		t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body))
	}
}

func TestConnectWithMultiTenancy(t *testing.T) {
	responseText := "Hello World"
	itemID := "theID"
	connectStorage := &ConnecterRESTStorage{
		connectHandler: &OutputConnect{
			response: responseText,
		},
	}

	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + itemID + "/connect",
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{
			"simple":         &SimpleRESTStorage{},
			"simple/connect": connectStorage,
		}
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testUrl)

		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Errorf("unexpected response: %#v", resp)
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if connectStorage.receivedID != itemID {
			t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
		}
		if string(body) != responseText {
			t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body))
		}
	}
}

func TestConnectResponderObject(t *testing.T) {
	itemID := "theID"
	simple := &genericapitesting.Simple{Other: "foo"}
	connectStorage := &ConnecterRESTStorage{}
	connectStorage.handlerFunc = func() http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			connectStorage.receivedResponder.Object(http.StatusCreated, simple)
		})
	}
	storage := map[string]rest.Storage{
		"simple":         &SimpleRESTStorage{},
		"simple/connect": connectStorage,
	}
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect")

	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusCreated {
		t.Errorf("unexpected response: %#v", resp)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if connectStorage.receivedID != itemID {
		t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
	}
	obj, err := runtime.Decode(codec, body)
	if err != nil {
		t.Fatal(err)
	}
	if !apiequality.Semantic.DeepEqual(obj, simple) {
		t.Errorf("Unexpected response: %#v", obj)
	}
}

func TestConnectResponderObjectWithMultiTenancy(t *testing.T) {
	itemID := "theID"
	simple := &genericapitesting.Simple{Other: "foo"}

	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + itemID + "/connect",
	}

	for _, testUrl := range testUrls {
		connectStorage := &ConnecterRESTStorage{}
		connectStorage.handlerFunc = func() http.Handler {
			return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
				connectStorage.receivedResponder.Object(http.StatusCreated, simple)
			})
		}
		storage := map[string]rest.Storage{
			"simple":         &SimpleRESTStorage{},
			"simple/connect": connectStorage,
		}
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testUrl)

		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusCreated {
			t.Errorf("unexpected response: %#v", resp)
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if connectStorage.receivedID != itemID {
			t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
		}
		obj, err := runtime.Decode(codec, body)
		if err != nil {
			t.Fatal(err)
		}
		if !apiequality.Semantic.DeepEqual(obj, simple) {
			t.Errorf("Unexpected response: %#v", obj)
		}
	}
}

func TestConnectResponderError(t *testing.T) {
	itemID := "theID"
	connectStorage := &ConnecterRESTStorage{}
	connectStorage.handlerFunc = func() http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			connectStorage.receivedResponder.Error(apierrs.NewForbidden(schema.GroupResource{Resource: "simples"}, itemID, errors.New("you are terminated")))
		})
	}
	storage := map[string]rest.Storage{
		"simple":         &SimpleRESTStorage{},
		"simple/connect": connectStorage,
	}
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect")

	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusForbidden {
		t.Errorf("unexpected response: %#v", resp)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if connectStorage.receivedID != itemID {
		t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
	}
	obj, err := runtime.Decode(codec, body)
	if err != nil {
		t.Fatal(err)
	}
	if obj.(*metav1.Status).Code != http.StatusForbidden {
		t.Errorf("Unexpected response: %#v", obj)
	}
}

func TestConnectResponderErrorWithMultiTenancy(t *testing.T) {
	itemID := "theID"
	connectStorage := &ConnecterRESTStorage{}
	connectStorage.handlerFunc = func() http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			connectStorage.receivedResponder.Error(apierrs.NewForbidden(schema.GroupResource{Resource: "simples"}, itemID, errors.New("you are terminated")))
		})
	}

	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + itemID + "/connect",
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{
			"simple":         &SimpleRESTStorage{},
			"simple/connect": connectStorage,
		}
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testUrl)

		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusForbidden {
			t.Errorf("unexpected response: %#v", resp)
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if connectStorage.receivedID != itemID {
			t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
		}
		obj, err := runtime.Decode(codec, body)
		if err != nil {
			t.Fatal(err)
		}
		if obj.(*metav1.Status).Code != http.StatusForbidden {
			t.Errorf("Unexpected response: %#v", obj)
		}
	}
}

func TestConnectWithOptionsRouteParams(t *testing.T) {
	connectStorage := &ConnecterRESTStorage{
		connectHandler:      &OutputConnect{},
		emptyConnectOptions: &genericapitesting.SimpleGetOptions{},
	}
	storage := map[string]rest.Storage{
		"simple":         &SimpleRESTStorage{},
		"simple/connect": connectStorage,
	}
	handler := handle(storage)
	ws := handler.(*defaultAPIServer).container.RegisteredWebServices()
	if len(ws) == 0 {
		t.Fatal("no web services registered")
	}
	routes := ws[0].Routes()
	for i := range routes {
		switch routes[i].Operation {
		case "connectGetNamespacedSimpleConnect":
		case "connectPostNamespacedSimpleConnect":
		case "connectPutNamespacedSimpleConnect":
		case "connectDeleteNamespacedSimpleConnect":
			validateSimpleGetOptionsParams(t, &routes[i])

		}
	}
}

func TestConnectWithOptions(t *testing.T) {
	responseText := "Hello World"
	itemID := "theID"
	connectStorage := &ConnecterRESTStorage{
		connectHandler: &OutputConnect{
			response: responseText,
		},
		emptyConnectOptions: &genericapitesting.SimpleGetOptions{},
	}
	storage := map[string]rest.Storage{
		"simple":         &SimpleRESTStorage{},
		"simple/connect": connectStorage,
	}
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect?param1=value1&param2=value2")

	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Errorf("unexpected response: %#v", resp)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if connectStorage.receivedID != itemID {
		t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
	}
	if string(body) != responseText {
		t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body))
	}
	if connectStorage.receivedResponder == nil {
		t.Errorf("Unexpected responder")
	}
	opts, ok := connectStorage.receivedConnectOptions.(*genericapitesting.SimpleGetOptions)
	if !ok {
		t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions)
	}
	if opts.Param1 != "value1" && opts.Param2 != "value2" {
		t.Errorf("Unexpected options value: %#v", opts)
	}
}

func TestConnectWithOptionsWithMultiTenancy(t *testing.T) {
	responseText := "Hello World"
	itemID := "theID"
	connectStorage := &ConnecterRESTStorage{
		connectHandler: &OutputConnect{
			response: responseText,
		},
		emptyConnectOptions: &genericapitesting.SimpleGetOptions{},
	}

	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + itemID + "/connect?param1=value1&param2=value2",
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{
			"simple":         &SimpleRESTStorage{},
			"simple/connect": connectStorage,
		}
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testUrl)

		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Errorf("unexpected response: %#v", resp)
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if connectStorage.receivedID != itemID {
			t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
		}
		if string(body) != responseText {
			t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body))
		}
		if connectStorage.receivedResponder == nil {
			t.Errorf("Unexpected responder")
		}
		opts, ok := connectStorage.receivedConnectOptions.(*genericapitesting.SimpleGetOptions)
		if !ok {
			t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions)
		}
		if opts.Param1 != "value1" && opts.Param2 != "value2" {
			t.Errorf("Unexpected options value: %#v", opts)
		}
	}
}

func TestConnectWithOptionsAndPath(t *testing.T) {
	responseText := "Hello World"
	itemID := "theID"
	testPath := "/a/b/c/def"
	connectStorage := &ConnecterRESTStorage{
		connectHandler: &OutputConnect{
			response: responseText,
		},
		emptyConnectOptions: &genericapitesting.SimpleGetOptions{},
		takesPath:           "atAPath",
	}
	storage := map[string]rest.Storage{
		"simple":         &SimpleRESTStorage{},
		"simple/connect": connectStorage,
	}
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect" + testPath + "?param1=value1&param2=value2")

	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Errorf("unexpected response: %#v", resp)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if connectStorage.receivedID != itemID {
		t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
	}
	if string(body) != responseText {
		t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body))
	}
	opts, ok := connectStorage.receivedConnectOptions.(*genericapitesting.SimpleGetOptions)
	if !ok {
		t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions)
	}
	if opts.Param1 != "value1" && opts.Param2 != "value2" {
		t.Errorf("Unexpected options value: %#v", opts)
	}
	if opts.Path != testPath {
		t.Errorf("Unexpected path value. Expected: %s. Actual: %s.", testPath, opts.Path)
	}
}

func TestConnectWithOptionsAndPathWithMultiTenancy(t *testing.T) {
	responseText := "Hello World"
	itemID := "theID"
	testPath := "/a/b/c/def"
	connectStorage := &ConnecterRESTStorage{
		connectHandler: &OutputConnect{
			response: responseText,
		},
		emptyConnectOptions: &genericapitesting.SimpleGetOptions{},
		takesPath:           "atAPath",
	}
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + itemID + "/connect" + testPath + "?param1=value1&param2=value2",
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{
			"simple":         &SimpleRESTStorage{},
			"simple/connect": connectStorage,
		}
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		resp, err := http.Get(server.URL + testUrl)

		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Errorf("unexpected response: %#v", resp)
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if connectStorage.receivedID != itemID {
			t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID)
		}
		if string(body) != responseText {
			t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body))
		}
		opts, ok := connectStorage.receivedConnectOptions.(*genericapitesting.SimpleGetOptions)
		if !ok {
			t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions)
		}
		if opts.Param1 != "value1" && opts.Param2 != "value2" {
			t.Errorf("Unexpected options value: %#v", opts)
		}
		if opts.Path != testPath {
			t.Errorf("Unexpected path value. Expected: %s. Actual: %s.", testPath, opts.Path)
		}
	}
}

func TestDelete(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	client := http.Client{}
	request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil)
	res, err := client.Do(request)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if res.StatusCode != http.StatusOK {
		t.Errorf("unexpected response: %#v", res)
	}
	if simpleStorage.deleted != ID {
		t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
	}
}

func TestDeleteWithMultiTenancy(t *testing.T) {
	ID := "id"
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + ID,
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{}

		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		client := http.Client{}
		request, err := http.NewRequest("DELETE", server.URL+testUrl, nil)
		res, err := client.Do(request)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if res.StatusCode != http.StatusOK {
			t.Errorf("unexpected response: %#v", res)
		}
		if simpleStorage.deleted != ID {
			t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
		}
	}
}

func TestDeleteWithOptions(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	grace := int64(300)
	item := &metav1.DeleteOptions{
		GracePeriodSeconds: &grace,
	}
	body, err := runtime.Encode(codec, item)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
	res, err := client.Do(request)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if res.StatusCode != http.StatusOK {
		t.Errorf("unexpected response: %s %#v", request.URL, res)
		s, err := ioutil.ReadAll(res.Body)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		t.Logf(string(s))
	}
	if simpleStorage.deleted != ID {
		t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
	}
	simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
	if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) {
		t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item))
	}
}

func TestDeleteWithOptionsWithMultiTenancy(t *testing.T) {
	ID := "id"
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + ID,
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{}
		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		grace := int64(300)
		item := &metav1.DeleteOptions{
			GracePeriodSeconds: &grace,
		}
		body, err := runtime.Encode(codec, item)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		client := http.Client{}
		request, err := http.NewRequest("DELETE", server.URL+testUrl, bytes.NewReader(body))
		res, err := client.Do(request)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if res.StatusCode != http.StatusOK {
			t.Errorf("unexpected response: %s %#v", request.URL, res)
			s, err := ioutil.ReadAll(res.Body)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			t.Logf(string(s))
		}
		if simpleStorage.deleted != ID {
			t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
		}
		simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
		if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) {
			t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item))
		}
	}
}

func TestDeleteWithOptionsQuery(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	grace := int64(300)
	item := &metav1.DeleteOptions{
		GracePeriodSeconds: &grace,
	}

	client := http.Client{}
	request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID+"?gracePeriodSeconds="+strconv.FormatInt(grace, 10), nil)
	res, err := client.Do(request)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if res.StatusCode != http.StatusOK {
		t.Fatalf("unexpected response: %s %#v", request.URL, res)
		s, err := ioutil.ReadAll(res.Body)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		t.Logf(string(s))
	}
	if simpleStorage.deleted != ID {
		t.Fatalf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
	}
	simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
	if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) {
		t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item))
	}
}

func TestDeleteWithOptionsQueryWithMultiTenancy(t *testing.T) {
	ID := "id"
	grace := int64(300)
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + ID + "?gracePeriodSeconds=" + strconv.FormatInt(grace, 10),
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{}
		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		item := &metav1.DeleteOptions{
			GracePeriodSeconds: &grace,
		}

		client := http.Client{}
		request, err := http.NewRequest("DELETE", server.URL+testUrl, nil)
		res, err := client.Do(request)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if res.StatusCode != http.StatusOK {
			t.Fatalf("unexpected response: %s %#v", request.URL, res)
			s, err := ioutil.ReadAll(res.Body)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			t.Logf(string(s))
		}
		if simpleStorage.deleted != ID {
			t.Fatalf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
		}
		simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
		if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) {
			t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item))
		}
	}
}

func TestDeleteWithOptionsQueryAndBody(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	grace := int64(300)
	item := &metav1.DeleteOptions{
		GracePeriodSeconds: &grace,
	}
	body, err := runtime.Encode(codec, item)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	client := http.Client{}
	request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID+"?gracePeriodSeconds="+strconv.FormatInt(grace+10, 10), bytes.NewReader(body))
	res, err := client.Do(request)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if res.StatusCode != http.StatusOK {
		t.Errorf("unexpected response: %s %#v", request.URL, res)
		s, err := ioutil.ReadAll(res.Body)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		t.Logf(string(s))
	}
	if simpleStorage.deleted != ID {
		t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
	}
	simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
	if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) {
		t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item))
	}
}

func TestDeleteWithOptionsQueryAndBodyWithMultiTenancy(t *testing.T) {
	ID := "id"
	grace := int64(300)
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + ID + "?gracePeriodSeconds=" + strconv.FormatInt(grace, 10),
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{}
		ID := "id"
		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		grace := int64(300)
		item := &metav1.DeleteOptions{
			GracePeriodSeconds: &grace,
		}
		body, err := runtime.Encode(codec, item)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		client := http.Client{}
		request, err := http.NewRequest("DELETE", server.URL+testUrl, bytes.NewReader(body))
		res, err := client.Do(request)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if res.StatusCode != http.StatusOK {
			t.Errorf("unexpected response: %s %#v", request.URL, res)
			s, err := ioutil.ReadAll(res.Body)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			t.Logf(string(s))
		}
		if simpleStorage.deleted != ID {
			t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
		}
		simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
		if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) {
			t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item))
		}
	}
}

func TestDeleteInvokesAdmissionControl(t *testing.T) {
	// TODO: remove mutating deny when we removed it from the endpoint implementation and ported all plugins
	for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} {
		t.Logf("Testing %T", admit)

		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{}
		ID := "id"
		storage["simple"] = &simpleStorage
		handler := handleInternal(storage, admit, selfLinker, nil)
		server := httptest.NewServer(handler)
		defer server.Close()

		client := http.Client{}
		request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil)
		response, err := client.Do(request)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if response.StatusCode != http.StatusForbidden {
			t.Errorf("Unexpected response %#v", response)
		}
	}
}

func TestDeleteInvokesAdmissionControlWithMultiTenancy(t *testing.T) {
	// TODO: remove mutating deny when we removed it from the endpoint implementation and ported all plugins
	ID := "id"
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + ID,
	}

	for _, testUrl := range testUrls {
		for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} {
			t.Logf("Testing %T", admit)

			storage := map[string]rest.Storage{}
			simpleStorage := SimpleRESTStorage{}

			storage["simple"] = &simpleStorage
			handler := handleInternal(storage, admit, selfLinker, nil)
			server := httptest.NewServer(handler)
			defer server.Close()

			client := http.Client{}
			request, err := http.NewRequest("DELETE", server.URL+testUrl, nil)
			response, err := client.Do(request)
			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if response.StatusCode != http.StatusForbidden {
				t.Errorf("Unexpected response %#v", response)
			}
		}
	}
}

func TestDeleteMissing(t *testing.T) {
	storage := map[string]rest.Storage{}
	ID := "id"
	simpleStorage := SimpleRESTStorage{
		errors: map[string]error{"delete": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, ID)},
	}
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	client := http.Client{}
	request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil)
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	if response.StatusCode != http.StatusNotFound {
		t.Errorf("Unexpected response %#v", response)
	}
}

func TestDeleteMissingWithMultiTenancy(t *testing.T) {
	ID := "id"
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + ID,
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{
			errors: map[string]error{"delete": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, ID)},
		}
		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		client := http.Client{}
		request, err := http.NewRequest("DELETE", server.URL+testUrl, nil)
		response, err := client.Do(request)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		if response.StatusCode != http.StatusNotFound {
			t.Errorf("Unexpected response %#v", response)
		}
	}
}

func TestUpdate(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + ID,
		name:        ID,
		namespace:   metav1.NamespaceDefault,
	}
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ID,
			Namespace: "", // update should allow the client to send an empty namespace
		},
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		// The following cases will fail, so die now
		t.Fatalf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	dump, _ := httputil.DumpResponse(response, true)
	t.Log(string(dump))

	if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name {
		t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestUpdateWithMultiTenancy(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	selfLinker := &setTestSelfLinker{
		t:           t,
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + ID,
		name:        ID,
		namespace:   metav1.NamespaceDefault,
		tenant:      "test",
	}
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ID,
			Namespace: "", // update should allow the client to send an empty namespace
		},
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		// The following cases will fail, so die now
		t.Fatalf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/tenants/test/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	dump, _ := httputil.DumpResponse(response, true)
	t.Log(string(dump))

	if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name {
		t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestUpdateInvokesAdmissionControl(t *testing.T) {
	for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} {
		t.Logf("Testing %T", admit)

		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{}
		ID := "id"
		storage["simple"] = &simpleStorage
		handler := handleInternal(storage, admit, selfLinker, nil)
		server := httptest.NewServer(handler)
		defer server.Close()

		item := &genericapitesting.Simple{
			ObjectMeta: metav1.ObjectMeta{
				Name:      ID,
				Namespace: metav1.NamespaceDefault,
			},
			Other: "bar",
		}
		body, err := runtime.Encode(testCodec, item)
		if err != nil {
			// The following cases will fail, so die now
			t.Fatalf("unexpected error: %v", err)
		}

		client := http.Client{}
		request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
		response, err := client.Do(request)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		dump, _ := httputil.DumpResponse(response, true)
		t.Log(string(dump))

		if response.StatusCode != http.StatusForbidden {
			t.Errorf("Unexpected response %#v", response)
		}
	}
}

func TestUpdateInvokesAdmissionControlWithMultiTenancy(t *testing.T) {
	ID := "id"
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/system/namespaces/default/simple/" + ID,
	}

	for _, testUrl := range testUrls {
		for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} {
			t.Logf("Testing %T", admit)

			storage := map[string]rest.Storage{}
			simpleStorage := SimpleRESTStorage{}
			storage["simple"] = &simpleStorage
			handler := handleInternal(storage, admit, selfLinker, nil)
			server := httptest.NewServer(handler)
			defer server.Close()

			item := &genericapitesting.Simple{
				ObjectMeta: metav1.ObjectMeta{
					Name:      ID,
					Namespace: metav1.NamespaceDefault,
					Tenant:    metav1.TenantSystem,
				},
				Other: "bar",
			}
			body, err := runtime.Encode(testCodec, item)
			if err != nil {
				// The following cases will fail, so die now
				t.Fatalf("unexpected error: %v", err)
			}

			client := http.Client{}
			request, err := http.NewRequest("PUT", server.URL+testUrl, bytes.NewReader(body))
			response, err := client.Do(request)
			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			dump, _ := httputil.DumpResponse(response, true)
			t.Log(string(dump))

			if response.StatusCode != http.StatusForbidden {
				t.Errorf("Unexpected response %#v", response)
			}
		}
	}
}

func TestUpdateRequiresMatchingName(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		// The following cases will fail, so die now
		t.Fatalf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusBadRequest {
		dump, _ := httputil.DumpResponse(response, true)
		t.Log(string(dump))
		t.Errorf("Unexpected response %#v", response)
	}
}

func TestUpdateRequiresMatchingNameWithMultiTenancy(t *testing.T) {
	ID := "id"
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + ID,
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{}
		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		item := &genericapitesting.Simple{
			Other: "bar",
		}
		body, err := runtime.Encode(testCodec, item)
		if err != nil {
			// The following cases will fail, so die now
			t.Fatalf("unexpected error: %v", err)
		}

		client := http.Client{}
		request, err := http.NewRequest("PUT", server.URL+testUrl, bytes.NewReader(body))
		response, err := client.Do(request)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if response.StatusCode != http.StatusBadRequest {
			dump, _ := httputil.DumpResponse(response, true)
			t.Log(string(dump))
			t.Errorf("Unexpected response %#v", response)
		}
	}
}

func TestUpdateAllowsMissingNamespace(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{
			Name: ID,
		},
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		// The following cases will fail, so die now
		t.Fatalf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	dump, _ := httputil.DumpResponse(response, true)
	t.Log(string(dump))

	if response.StatusCode != http.StatusOK {
		t.Errorf("Unexpected response %#v", response)
	}
}

func TestUpdateAllowsMissingNamespaceWithMultiTenancy(t *testing.T) {
	ID := "id"
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/" + ID,
	}

	for _, testUrl := range testUrls {
		storage := map[string]rest.Storage{}
		simpleStorage := SimpleRESTStorage{}
		storage["simple"] = &simpleStorage
		handler := handle(storage)
		server := httptest.NewServer(handler)
		defer server.Close()

		item := &genericapitesting.Simple{
			ObjectMeta: metav1.ObjectMeta{
				Name: ID,
			},
			Other: "bar",
		}
		body, err := runtime.Encode(testCodec, item)
		if err != nil {
			// The following cases will fail, so die now
			t.Fatalf("unexpected error: %v", err)
		}

		client := http.Client{}
		request, err := http.NewRequest("PUT", server.URL+testUrl, bytes.NewReader(body))
		response, err := client.Do(request)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		dump, _ := httputil.DumpResponse(response, true)
		t.Log(string(dump))

		if response.StatusCode != http.StatusOK {
			t.Errorf("Unexpected response %#v", response)
		}
	}
}

// when the object name and namespace can't be retrieved, don't update.  It isn't safe.
func TestUpdateDisallowsMismatchedNamespaceOnError(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	selfLinker := &setTestSelfLinker{
		t:   t,
		err: fmt.Errorf("test error"),
	}
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ID,
			Namespace: "other", // does not match request
		},
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		// The following cases will fail, so die now
		t.Fatalf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	dump, _ := httputil.DumpResponse(response, true)
	t.Log(string(dump))

	if simpleStorage.updated != nil {
		t.Errorf("Unexpected update value %#v.", simpleStorage.updated)
	}
	if selfLinker.called {
		t.Errorf("self link ignored")
	}
}

// when the object name and namespace can't be retrieved, don't update.  It isn't safe.
func TestUpdateDisallowsMismatchedNamespaceOnErrorWithMultiTenancy(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	selfLinker := &setTestSelfLinker{
		t:   t,
		err: fmt.Errorf("test error"),
	}
	handler := handleLinker(storage, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ID,
			Namespace: "other", // does not match request
			Tenant:    "else",
		},
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		// The following cases will fail, so die now
		t.Fatalf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/tenants/system/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	dump, _ := httputil.DumpResponse(response, true)
	t.Log(string(dump))

	if simpleStorage.updated != nil {
		t.Errorf("Unexpected update value %#v.", simpleStorage.updated)
	}
	if selfLinker.called {
		t.Errorf("self link ignored")
	}
}

func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ID,
			Namespace: "other",
		},
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		// The following cases will fail, so die now
		t.Fatalf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusBadRequest {
		t.Errorf("Unexpected response %#v", response)
	}
}

func TestUpdatePreventsMismatchedNamespaceWithMultiTenancy(t *testing.T) {
	storage := map[string]rest.Storage{}
	simpleStorage := SimpleRESTStorage{}
	ID := "id"
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ID,
			Namespace: "other",
			Tenant:    "else",
		},
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		// The following cases will fail, so die now
		t.Fatalf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/tenants/system/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusBadRequest {
		t.Errorf("Unexpected response %#v", response)
	}
}

func TestUpdateMissing(t *testing.T) {
	storage := map[string]rest.Storage{}
	ID := "id"
	simpleStorage := SimpleRESTStorage{
		errors: map[string]error{"update": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, ID)},
	}
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ID,
			Namespace: metav1.NamespaceDefault,
		},
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusNotFound {
		t.Errorf("Unexpected response %#v", response)
	}
}

func TestUpdateMissingWithMultiTenancy(t *testing.T) {
	storage := map[string]rest.Storage{}
	ID := "id"
	simpleStorage := SimpleRESTStorage{
		errors: map[string]error{"update": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, ID)},
	}
	storage["simple"] = &simpleStorage
	handler := handle(storage)
	server := httptest.NewServer(handler)
	defer server.Close()

	item := &genericapitesting.Simple{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ID,
			Namespace: metav1.NamespaceDefault,
			Tenant:    metav1.TenantSystem,
		},
		Other: "bar",
	}
	body, err := runtime.Encode(testCodec, item)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	client := http.Client{}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/tenants/system/namespaces/default/simple/"+ID, bytes.NewReader(body))
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusNotFound {
		t.Errorf("Unexpected response %#v", response)
	}
}

func TestCreateNotFound(t *testing.T) {
	handler := handle(map[string]rest.Storage{
		"simple": &SimpleRESTStorage{
			// storage.Create can fail with not found error in theory.
			// See http://pr.k8s.io/486#discussion_r15037092.
			errors: map[string]error{"create": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, "id")},
		},
	})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &genericapitesting.Simple{Other: "foo"}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	if response.StatusCode != http.StatusNotFound {
		t.Errorf("Unexpected response %#v", response)
	}
}

func TestCreateNotFoundWithMultiTenancy(t *testing.T) {
	handler := handle(map[string]rest.Storage{
		"simple": &SimpleRESTStorage{
			// storage.Create can fail with not found error in theory.
			// See http://pr.k8s.io/486#discussion_r15037092.
			errors: map[string]error{"create": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, "id")},
		},
	})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &genericapitesting.Simple{Other: "foo"}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/tenants/system/namespaces/default/simple", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	if response.StatusCode != http.StatusNotFound {
		t.Errorf("Unexpected response %#v", response)
	}
}

func TestCreateChecksDecode(t *testing.T) {
	handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &example.Pod{}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusBadRequest {
		t.Errorf("Unexpected response %#v", response)
	}
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	} else if !strings.Contains(string(b), "cannot be handled as a Simple") {
		t.Errorf("unexpected response: %s", string(b))
	}
}

func TestCreateChecksDecodeWithMultiTenancy(t *testing.T) {
	handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &example.Pod{}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/tenants/system/namespaces/default/simple", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusBadRequest {
		t.Errorf("Unexpected response %#v", response)
	}
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	} else if !strings.Contains(string(b), "cannot be handled as a Simple") {
		t.Errorf("unexpected response: %s", string(b))
	}
}

func TestParentResourceIsRequired(t *testing.T) {
	storage := &SimpleTypedStorage{
		baseType: &genericapitesting.SimpleRoot{}, // a root scoped type
		item:     &genericapitesting.SimpleRoot{},
	}
	group := &APIGroupVersion{
		Storage: map[string]rest.Storage{
			"simple/sub": storage,
		},
		Root:            "/" + prefix,
		Creater:         scheme,
		Convertor:       scheme,
		UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme),
		Defaulter:       scheme,
		Typer:           scheme,
		Linker:          selfLinker,
		RootScopedKinds: sets.NewString("SimpleRoot"),

		EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(),

		Admit: admissionControl,

		GroupVersion:           newGroupVersion,
		OptionsExternalVersion: &newGroupVersion,

		Serializer:     codecs,
		ParameterCodec: parameterCodec,
	}
	container := restful.NewContainer()
	if err := group.InstallREST(container); err == nil {
		t.Fatal("expected error")
	}

	storage = &SimpleTypedStorage{
		baseType: &genericapitesting.SimpleRoot{}, // a root scoped type
		item:     &genericapitesting.SimpleRoot{},
	}
	group = &APIGroupVersion{
		Storage: map[string]rest.Storage{
			"simple":     &SimpleRESTStorage{},
			"simple/sub": storage,
		},
		Root:            "/" + prefix,
		Creater:         scheme,
		Convertor:       scheme,
		UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme),
		Defaulter:       scheme,
		Typer:           scheme,
		Linker:          selfLinker,

		EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(),

		Admit: admissionControl,

		GroupVersion:           newGroupVersion,
		OptionsExternalVersion: &newGroupVersion,

		Serializer:     codecs,
		ParameterCodec: parameterCodec,
	}
	container = restful.NewContainer()
	if err := group.InstallREST(container); err != nil {
		t.Fatal(err)
	}

	handler := genericapifilters.WithRequestInfo(container, newTestRequestInfoResolver())

	// resource is NOT registered in the root scope
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/simple/test/sub"}})
	if w.Code != http.StatusNotFound {
		t.Errorf("expected not found: %#v", w)
	}

	// resource is registered in the namespace scope
	w = httptest.NewRecorder()
	handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/simple/test/sub"}})
	if w.Code != http.StatusOK {
		t.Fatalf("expected OK: %#v", w)
	}
	if storage.actualNamespace != "test" {
		t.Errorf("namespace should be set %#v", storage)
	}
}

func TestParentResourceIsRequiredWithMultiTenancy(t *testing.T) {
	storage := &SimpleTypedStorage{
		baseType: &genericapitesting.SimpleRoot{}, // a root scoped type
		item:     &genericapitesting.SimpleRoot{},
	}
	group := &APIGroupVersion{
		Storage: map[string]rest.Storage{
			"simple/sub": storage,
		},
		Root:            "/" + prefix,
		Creater:         scheme,
		Convertor:       scheme,
		UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme),
		Defaulter:       scheme,
		Typer:           scheme,
		Linker:          selfLinker,
		RootScopedKinds: sets.NewString("SimpleRoot"),

		EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(),

		Admit: admissionControl,

		GroupVersion:           newGroupVersion,
		OptionsExternalVersion: &newGroupVersion,

		Serializer:     codecs,
		ParameterCodec: parameterCodec,
	}
	container := restful.NewContainer()
	if err := group.InstallREST(container); err == nil {
		t.Fatal("expected error")
	}

	storage = &SimpleTypedStorage{
		baseType: &genericapitesting.SimpleRoot{}, // a root scoped type
		item:     &genericapitesting.SimpleRoot{},
	}
	group = &APIGroupVersion{
		Storage: map[string]rest.Storage{
			"simple":     &SimpleRESTStorage{},
			"simple/sub": storage,
		},
		Root:            "/" + prefix,
		Creater:         scheme,
		Convertor:       scheme,
		UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme),
		Defaulter:       scheme,
		Typer:           scheme,
		Linker:          selfLinker,

		EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(),

		Admit: admissionControl,

		GroupVersion:           newGroupVersion,
		OptionsExternalVersion: &newGroupVersion,

		Serializer:     codecs,
		ParameterCodec: parameterCodec,
	}
	container = restful.NewContainer()
	if err := group.InstallREST(container); err != nil {
		t.Fatal(err)
	}

	handler := genericapifilters.WithRequestInfo(container, newTestRequestInfoResolver())

	// resource is NOT registered in the root scope
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/simple/test/sub"}})
	if w.Code != http.StatusNotFound {
		t.Errorf("expected not found: %#v", w)
	}

	// resource is registered in the tenant/namespace scope
	w = httptest.NewRecorder()
	handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/te/namespaces/ns/simple/test/sub"}})
	if w.Code != http.StatusOK {
		t.Fatalf("expected OK: %#v", w)
	}
	if storage.actualNamespace != "ns" {
		t.Errorf("namespace should be set %#v", storage)
	}
	if storage.actualTenant != "te" {
		t.Errorf("tenant should be set %#v", storage)
	}
}

func TestCreateWithName(t *testing.T) {
	pathName := "helloworld"
	storage := &NamedCreaterRESTStorage{SimpleRESTStorage: &SimpleRESTStorage{}}
	handler := handle(map[string]rest.Storage{
		"simple":     &SimpleRESTStorage{},
		"simple/sub": storage,
	})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &genericapitesting.Simple{Other: "foo"}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+pathName+"/sub", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusCreated {
		t.Errorf("Unexpected response %#v", response)
	}
	if storage.createdName != pathName {
		t.Errorf("Did not get expected name in create context. Got: %s, Expected: %s", storage.createdName, pathName)
	}
}

func TestCreateWithNameWithMultiTenancy(t *testing.T) {
	pathName := "helloworld"
	storage := &NamedCreaterRESTStorage{SimpleRESTStorage: &SimpleRESTStorage{}}
	handler := handle(map[string]rest.Storage{
		"simple":     &SimpleRESTStorage{},
		"simple/sub": storage,
	})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &genericapitesting.Simple{Other: "foo"}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/tenants/system/namespaces/default/simple/"+pathName+"/sub", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusCreated {
		t.Errorf("Unexpected response %#v", response)
	}
	if storage.createdName != pathName {
		t.Errorf("Did not get expected name in create context. Got: %s, Expected: %s", storage.createdName, pathName)
	}
}

func TestUpdateChecksDecode(t *testing.T) {
	handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &example.Pod{}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/bar", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusBadRequest {
		t.Errorf("Unexpected response %#v\n%s", response, readBodyOrDie(response.Body))
	}
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	} else if !strings.Contains(string(b), "cannot be handled as a Simple") {
		t.Errorf("unexpected response: %s", string(b))
	}
}

func TestUpdateChecksDecodeWithMultiTenancy(t *testing.T) {
	handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &example.Pod{}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/tenants/system/namespaces/default/simple/bar", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusBadRequest {
		t.Errorf("Unexpected response %#v\n%s", response, readBodyOrDie(response.Body))
	}
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	} else if !strings.Contains(string(b), "cannot be handled as a Simple") {
		t.Errorf("unexpected response: %s", string(b))
	}
}

type setTestSelfLinker struct {
	t              *testing.T
	expectedSet    string
	alternativeSet sets.String
	name           string
	namespace      string
	tenant         string
	called         bool
	err            error
}

func (s *setTestSelfLinker) Tenant(runtime.Object) (string, error)    { return s.tenant, s.err }
func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, s.err }
func (s *setTestSelfLinker) Name(runtime.Object) (string, error)      { return s.name, s.err }
func (s *setTestSelfLinker) SelfLink(runtime.Object) (string, error)  { return "", s.err }
func (s *setTestSelfLinker) SetSelfLink(obj runtime.Object, selfLink string) error {
	if e, a := s.expectedSet, selfLink; e != a {
		if !s.alternativeSet.Has(a) {
			s.t.Errorf("expected '%v', got '%v'", e, a)
		}
	}
	s.called = true
	return s.err
}

func TestCreate(t *testing.T) {
	storage := SimpleRESTStorage{
		injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
			time.Sleep(5 * time.Millisecond)
			return obj, nil
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		name:        "bar",
		namespace:   "default",
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar",
	}
	handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &genericapitesting.Simple{
		Other: "bar",
	}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	wg := sync.WaitGroup{}
	wg.Add(1)
	var response *http.Response
	go func() {
		response, err = client.Do(request)
		wg.Done()
	}()
	wg.Wait()
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	var itemOut genericapitesting.Simple
	body, err := extractBody(response, &itemOut)
	if err != nil {
		t.Errorf("unexpected error: %v %#v", err, response)
	}

	itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
	if !reflect.DeepEqual(&itemOut, simple) {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
	}
	if response.StatusCode != http.StatusCreated {
		t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestCreateWithMultiTenancy(t *testing.T) {
	testCases := []struct {
		url       string
		tenant    string
		namespace string
		name      string
		selfLink  string
	}{
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo",
			tenant:    "test",
			namespace: "default",
			name:      "bar",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo/bar",
		},
	}

	for _, testCase := range testCases {
		storage := SimpleRESTStorage{
			injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
				time.Sleep(5 * time.Millisecond)
				return obj, nil
			},
		}
		selfLinker := &setTestSelfLinker{
			t:           t,
			name:        testCase.name,
			namespace:   testCase.namespace,
			tenant:      testCase.tenant,
			expectedSet: testCase.selfLink,
		}
		handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker)
		server := httptest.NewServer(handler)
		defer server.Close()
		client := http.Client{}

		simple := &genericapitesting.Simple{
			Other: "bar",
		}
		data, err := runtime.Encode(testCodec, simple)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		request, err := http.NewRequest("POST", server.URL+testCase.url, bytes.NewBuffer(data))
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		wg := sync.WaitGroup{}
		wg.Add(1)
		var response *http.Response
		go func() {
			response, err = client.Do(request)
			wg.Done()
		}()
		wg.Wait()
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		var itemOut genericapitesting.Simple
		body, err := extractBody(response, &itemOut)
		if err != nil {
			t.Errorf("unexpected error: %v %#v", err, response)
		}

		itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
		if !reflect.DeepEqual(&itemOut, simple) {
			t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
		}
		if response.StatusCode != http.StatusCreated {
			t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
		}
		if !selfLinker.called {
			t.Errorf("Never set self link")
		}
	}
}

func TestCreateYAML(t *testing.T) {
	storage := SimpleRESTStorage{
		injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
			time.Sleep(5 * time.Millisecond)
			return obj, nil
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		name:        "bar",
		namespace:   "default",
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar",
	}
	handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	// yaml encoder
	simple := &genericapitesting.Simple{
		Other: "bar",
	}
	info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), "application/yaml")
	if !ok {
		t.Fatal("No yaml serializer")
	}
	encoder := codecs.EncoderForVersion(info.Serializer, testGroupVersion)
	decoder := codecs.DecoderToVersion(info.Serializer, testInternalGroupVersion)

	data, err := runtime.Encode(encoder, simple)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	request.Header.Set("Accept", "application/yaml, application/json")
	request.Header.Set("Content-Type", "application/yaml")

	wg := sync.WaitGroup{}
	wg.Add(1)
	var response *http.Response
	go func() {
		response, err = client.Do(request)
		wg.Done()
	}()
	wg.Wait()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	var itemOut genericapitesting.Simple
	body, err := extractBodyDecoder(response, &itemOut, decoder)
	if err != nil {
		t.Fatalf("unexpected error: %v %#v", err, response)
	}

	itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
	if !reflect.DeepEqual(&itemOut, simple) {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
	}
	if response.StatusCode != http.StatusCreated {
		t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestCreateYAMLWithMultiTenancy(t *testing.T) {
	testCases := []struct {
		url       string
		tenant    string
		namespace string
		name      string
		selfLink  string
	}{
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo",
			tenant:    "test",
			namespace: "default",
			name:      "bar",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo/bar",
		},
	}

	for _, testCase := range testCases {
		storage := SimpleRESTStorage{
			injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
				time.Sleep(5 * time.Millisecond)
				return obj, nil
			},
		}
		selfLinker := &setTestSelfLinker{
			t:           t,
			name:        testCase.name,
			namespace:   testCase.namespace,
			tenant:      testCase.tenant,
			expectedSet: testCase.selfLink,
		}
		handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker)
		server := httptest.NewServer(handler)
		defer server.Close()
		client := http.Client{}

		// yaml encoder
		simple := &genericapitesting.Simple{
			Other: "bar",
		}
		info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), "application/yaml")
		if !ok {
			t.Fatal("No yaml serializer")
		}
		encoder := codecs.EncoderForVersion(info.Serializer, testGroupVersion)
		decoder := codecs.DecoderToVersion(info.Serializer, testInternalGroupVersion)

		data, err := runtime.Encode(encoder, simple)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		request, err := http.NewRequest("POST", server.URL+testCase.url, bytes.NewBuffer(data))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		request.Header.Set("Accept", "application/yaml, application/json")
		request.Header.Set("Content-Type", "application/yaml")

		wg := sync.WaitGroup{}
		wg.Add(1)
		var response *http.Response
		go func() {
			response, err = client.Do(request)
			wg.Done()
		}()
		wg.Wait()
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		var itemOut genericapitesting.Simple
		body, err := extractBodyDecoder(response, &itemOut, decoder)
		if err != nil {
			t.Fatalf("unexpected error: %v %#v", err, response)
		}

		itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
		if !reflect.DeepEqual(&itemOut, simple) {
			t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
		}
		if response.StatusCode != http.StatusCreated {
			t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
		}
		if !selfLinker.called {
			t.Errorf("Never set self link")
		}
	}
}

func TestCreateInNamespace(t *testing.T) {
	storage := SimpleRESTStorage{
		injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
			time.Sleep(5 * time.Millisecond)
			return obj, nil
		},
	}
	selfLinker := &setTestSelfLinker{
		t:           t,
		name:        "bar",
		namespace:   "other",
		expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar",
	}
	handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker)
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &genericapitesting.Simple{
		Other: "bar",
	}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	wg := sync.WaitGroup{}
	wg.Add(1)
	var response *http.Response
	go func() {
		response, err = client.Do(request)
		wg.Done()
	}()
	wg.Wait()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	var itemOut genericapitesting.Simple
	body, err := extractBody(response, &itemOut)
	if err != nil {
		t.Fatalf("unexpected error: %v\n%s", err, data)
	}

	itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
	if !reflect.DeepEqual(&itemOut, simple) {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
	}
	if response.StatusCode != http.StatusCreated {
		t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
	}
	if !selfLinker.called {
		t.Errorf("Never set self link")
	}
}

func TestCreateInNamespaceWithMultiTenancy(t *testing.T) {
	testCases := []struct {
		url       string
		tenant    string
		namespace string
		name      string
		selfLink  string
	}{
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo",
			tenant:    "test",
			namespace: "default",
			name:      "bar",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo/bar",
		},
	}

	for _, testCase := range testCases {
		storage := SimpleRESTStorage{
			injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
				time.Sleep(5 * time.Millisecond)
				return obj, nil
			},
		}
		selfLinker := &setTestSelfLinker{
			t:           t,
			name:        testCase.name,
			namespace:   testCase.namespace,
			tenant:      testCase.tenant,
			expectedSet: testCase.selfLink,
		}
		handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker)
		server := httptest.NewServer(handler)
		defer server.Close()
		client := http.Client{}

		simple := &genericapitesting.Simple{
			Other: "bar",
		}
		data, err := runtime.Encode(testCodec, simple)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		request, err := http.NewRequest("POST", server.URL+testCase.url, bytes.NewBuffer(data))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		wg := sync.WaitGroup{}
		wg.Add(1)
		var response *http.Response
		go func() {
			response, err = client.Do(request)
			wg.Done()
		}()
		wg.Wait()
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		var itemOut genericapitesting.Simple
		body, err := extractBody(response, &itemOut)
		if err != nil {
			t.Fatalf("unexpected error: %v\n%s", err, data)
		}

		itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
		if !reflect.DeepEqual(&itemOut, simple) {
			t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
		}
		if response.StatusCode != http.StatusCreated {
			t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
		}
		if !selfLinker.called {
			t.Errorf("Never set self link")
		}
	}
}

func TestCreateInvokeAdmissionControl(t *testing.T) {
	for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} {
		t.Logf("Testing %T", admit)

		storage := SimpleRESTStorage{
			injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
				time.Sleep(5 * time.Millisecond)
				return obj, nil
			},
		}
		selfLinker := &setTestSelfLinker{
			t:           t,
			name:        "bar",
			namespace:   "other",
			expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar",
		}
		handler := handleInternal(map[string]rest.Storage{"foo": &storage}, admit, selfLinker, nil)
		server := httptest.NewServer(handler)
		defer server.Close()
		client := http.Client{}

		simple := &genericapitesting.Simple{
			Other: "bar",
		}
		data, err := runtime.Encode(testCodec, simple)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data))
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		wg := sync.WaitGroup{}
		wg.Add(1)
		var response *http.Response
		go func() {
			response, err = client.Do(request)
			wg.Done()
		}()
		wg.Wait()
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if response.StatusCode != http.StatusForbidden {
			t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response)
		}
	}
}

func TestCreateInvokeAdmissionControlWithMultiTenancy(t *testing.T) {
	testCases := []struct {
		url       string
		tenant    string
		namespace string
		name      string
		selfLink  string
	}{
		{
			url:       "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo",
			tenant:    "test",
			namespace: "default",
			name:      "bar",
			selfLink:  "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo/bar",
		},
	}

	for _, testCase := range testCases {
		for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} {
			t.Logf("Testing %T", admit)

			storage := SimpleRESTStorage{
				injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
					time.Sleep(5 * time.Millisecond)
					return obj, nil
				},
			}
			selfLinker := &setTestSelfLinker{
				t:           t,
				name:        testCase.name,
				namespace:   testCase.namespace,
				tenant:      testCase.tenant,
				expectedSet: testCase.selfLink,
			}
			handler := handleInternal(map[string]rest.Storage{"foo": &storage}, admit, selfLinker, nil)
			server := httptest.NewServer(handler)
			defer server.Close()
			client := http.Client{}

			simple := &genericapitesting.Simple{
				Other: "bar",
			}
			data, err := runtime.Encode(testCodec, simple)
			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			request, err := http.NewRequest("POST", server.URL+testCase.url, bytes.NewBuffer(data))
			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}

			wg := sync.WaitGroup{}
			wg.Add(1)
			var response *http.Response
			go func() {
				response, err = client.Do(request)
				wg.Done()
			}()
			wg.Wait()
			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if response.StatusCode != http.StatusForbidden {
				t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response)
			}
		}
	}
}

func expectApiStatus(t *testing.T, method, url string, data []byte, code int) *metav1.Status {
	t.Helper()
	client := http.Client{}
	request, err := http.NewRequest(method, url, bytes.NewBuffer(data))
	if err != nil {
		t.Fatalf("unexpected error %#v", err)
		return nil
	}
	response, err := client.Do(request)
	if err != nil {
		t.Fatalf("unexpected error on %s %s: %v", method, url, err)
		return nil
	}
	var status metav1.Status
	body, err := extractBody(response, &status)
	if err != nil {
		t.Fatalf("unexpected error on %s %s: %v\nbody:\n%s", method, url, err, body)
		return nil
	}
	if code != response.StatusCode {
		t.Fatalf("Expected %s %s to return %d, Got %d: %v", method, url, code, response.StatusCode, body)
	}
	return &status
}

func TestDelayReturnsError(t *testing.T) {
	storage := SimpleRESTStorage{
		injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
			return nil, apierrs.NewAlreadyExists(schema.GroupResource{Resource: "foos"}, "bar")
		},
	}
	handler := handle(map[string]rest.Storage{"foo": &storage})
	server := httptest.NewServer(handler)
	defer server.Close()

	status := expectApiStatus(t, "DELETE", fmt.Sprintf("%s/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo/bar", server.URL), nil, http.StatusConflict)
	if status.Status != metav1.StatusFailure || status.Message == "" || status.Details == nil || status.Reason != metav1.StatusReasonAlreadyExists {
		t.Errorf("Unexpected status %#v", status)
	}
}

func TestDelayReturnsErrorWithMultiTenancy(t *testing.T) {
	testUrls := []string{
		prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo/bar",
	}

	for _, testUrl := range testUrls {
		storage := SimpleRESTStorage{
			injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
				return nil, apierrs.NewAlreadyExists(schema.GroupResource{Resource: "foos"}, "bar")
			},
		}
		handler := handle(map[string]rest.Storage{"foo": &storage})
		server := httptest.NewServer(handler)
		defer server.Close()

		status := expectApiStatus(t, "DELETE", fmt.Sprintf("%s/"+testUrl, server.URL), nil, http.StatusConflict)
		if status.Status != metav1.StatusFailure || status.Message == "" || status.Details == nil || status.Reason != metav1.StatusReasonAlreadyExists {
			t.Errorf("Unexpected status %#v", status)
		}
	}
}

type UnregisteredAPIObject struct {
	Value string
}

func (obj *UnregisteredAPIObject) GetObjectKind() schema.ObjectKind {
	return schema.EmptyObjectKind
}
func (obj *UnregisteredAPIObject) DeepCopyObject() runtime.Object {
	if obj == nil {
		return nil
	}
	clone := *obj
	return &clone
}

func TestWriteJSONDecodeError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		responsewriters.WriteObjectNegotiated(codecs, negotiation.DefaultEndpointRestrictions, newGroupVersion, w, req, http.StatusOK, &UnregisteredAPIObject{"Undecodable"})
	}))
	defer server.Close()
	// Decode error response behavior is dictated by
	// apiserver/pkg/endpoints/handlers/responsewriters/status.go::ErrorToAPIStatus().
	// Unless specific metav1.Status() parameters are implemented for the particular error in question, such that
	// the status code is defined, metav1 errors where error.status == metav1.StatusFailure
	// will throw a '500 Internal Server Error'. Non-metav1 type errors will always throw a '500 Internal Server Error'.
	status := expectApiStatus(t, "GET", server.URL, nil, http.StatusInternalServerError)
	if status.Reason != metav1.StatusReasonUnknown {
		t.Errorf("unexpected reason %#v", status)
	}
	if !strings.Contains(status.Message, "no kind is registered for the type endpoints.UnregisteredAPIObject") {
		t.Errorf("unexpected message %#v", status)
	}
}

type marshalError struct {
	err error
}

func (m *marshalError) MarshalJSON() ([]byte, error) {
	return []byte{}, m.err
}

func TestWriteRAWJSONMarshalError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		responsewriters.WriteRawJSON(http.StatusOK, &marshalError{errors.New("Undecodable")}, w)
	}))
	defer server.Close()
	client := http.Client{}
	resp, err := client.Get(server.URL)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	if resp.StatusCode != http.StatusInternalServerError {
		t.Errorf("unexpected status code %d", resp.StatusCode)
	}
}

func TestCreateTimeout(t *testing.T) {
	testOver := make(chan struct{})
	defer close(testOver)
	storage := SimpleRESTStorage{
		injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
			// Eliminate flakes by ensuring the create operation takes longer than this test.
			<-testOver
			return obj, nil
		},
	}
	handler := handle(map[string]rest.Storage{
		"foo": &storage,
	})
	server := httptest.NewServer(handler)
	defer server.Close()

	simple := &genericapitesting.Simple{Other: "foo"}
	data, err := runtime.Encode(testCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	itemOut := expectApiStatus(t, "POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo?timeout=4ms", data, http.StatusGatewayTimeout)
	if itemOut.Status != metav1.StatusFailure || itemOut.Reason != metav1.StatusReasonTimeout {
		t.Errorf("Unexpected status %#v", itemOut)
	}
}

func TestCreateTimeoutWithMultiTenancy(t *testing.T) {
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/foo?timeout=4ms",
	}

	for _, testUrl := range testUrls {
		testOver := make(chan struct{})
		defer close(testOver)
		storage := SimpleRESTStorage{
			injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
				// Eliminate flakes by ensuring the create operation takes longer than this test.
				<-testOver
				return obj, nil
			},
		}
		handler := handle(map[string]rest.Storage{
			"foo": &storage,
		})
		server := httptest.NewServer(handler)
		defer server.Close()

		simple := &genericapitesting.Simple{Other: "foo"}
		data, err := runtime.Encode(testCodec, simple)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		itemOut := expectApiStatus(t, "POST", server.URL+testUrl, data, http.StatusGatewayTimeout)
		if itemOut.Status != metav1.StatusFailure || itemOut.Reason != metav1.StatusReasonTimeout {
			t.Errorf("Unexpected status %#v", itemOut)
		}
	}
}

func TestCreateChecksAPIVersionWithMultiTenancy(t *testing.T) {
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple",
	}

	for _, testUrl := range testUrls {
		handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
		server := httptest.NewServer(handler)
		defer server.Close()
		client := http.Client{}

		simple := &genericapitesting.Simple{}
		//using newCodec and send the request to testVersion URL shall cause a discrepancy in apiVersion
		data, err := runtime.Encode(newCodec, simple)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		request, err := http.NewRequest("POST", server.URL+testUrl, bytes.NewBuffer(data))
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		response, err := client.Do(request)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if response.StatusCode != http.StatusBadRequest {
			t.Errorf("Unexpected response %#v", response)
		}
		b, err := ioutil.ReadAll(response.Body)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		} else if !strings.Contains(string(b), "does not match the expected API version") {
			t.Errorf("unexpected response: %s", string(b))
		}
	}
}

func TestCreateChecksAPIVersion(t *testing.T) {
	handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &genericapitesting.Simple{}
	//using newCodec and send the request to testVersion URL shall cause a discrepancy in apiVersion
	data, err := runtime.Encode(newCodec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusBadRequest {
		t.Errorf("Unexpected response %#v", response)
	}
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	} else if !strings.Contains(string(b), "does not match the expected API version") {
		t.Errorf("unexpected response: %s", string(b))
	}
}

func TestCreateDefaultsAPIVersion(t *testing.T) {
	handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &genericapitesting.Simple{}
	data, err := runtime.Encode(codec, simple)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	m := make(map[string]interface{})
	if err := json.Unmarshal(data, &m); err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	delete(m, "apiVersion")
	data, err = json.Marshal(m)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	response, err := client.Do(request)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusCreated {
		t.Errorf("unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusCreated, response)
	}
}

func TestCreateDefaultsAPIVersionWithMultiTenancy(t *testing.T) {

	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple",
	}

	for _, testUrl := range testUrls {
		handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
		server := httptest.NewServer(handler)
		defer server.Close()
		client := http.Client{}

		simple := &genericapitesting.Simple{}
		data, err := runtime.Encode(codec, simple)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		m := make(map[string]interface{})
		if err := json.Unmarshal(data, &m); err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		delete(m, "apiVersion")
		data, err = json.Marshal(m)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		request, err := http.NewRequest("POST", server.URL+testUrl, bytes.NewBuffer(data))
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		response, err := client.Do(request)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if response.StatusCode != http.StatusCreated {
			t.Errorf("unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusCreated, response)
		}
	}
}

func TestUpdateChecksAPIVersion(t *testing.T) {
	handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	simple := &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}
	data, err := runtime.Encode(newCodec, simple)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/bar", bytes.NewBuffer(data))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	response, err := client.Do(request)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if response.StatusCode != http.StatusBadRequest {
		t.Errorf("Unexpected response %#v", response)
	}
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	} else if !strings.Contains(string(b), "does not match the expected API version") {
		t.Errorf("unexpected response: %s", string(b))
	}
}

func TestUpdateChecksAPIVersionWithMultiTenancy(t *testing.T) {
	testUrls := []string{
		"/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/test/namespaces/default/simple/bar",
	}

	for _, testUrl := range testUrls {
		handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
		server := httptest.NewServer(handler)
		defer server.Close()
		client := http.Client{}

		simple := &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}
		data, err := runtime.Encode(newCodec, simple)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		request, err := http.NewRequest("PUT", server.URL+testUrl, bytes.NewBuffer(data))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		response, err := client.Do(request)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if response.StatusCode != http.StatusBadRequest {
			t.Errorf("Unexpected response %#v", response)
		}
		b, err := ioutil.ReadAll(response.Body)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		} else if !strings.Contains(string(b), "does not match the expected API version") {
			t.Errorf("unexpected response: %s", string(b))
		}
	}
}

// runRequest is used by TestDryRun since it runs the test twice in a
// row with a slightly different URL (one has ?dryRun, one doesn't).
func runRequest(t *testing.T, path, verb string, data []byte, contentType string) *http.Response {
	request, err := http.NewRequest(verb, path, bytes.NewBuffer(data))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if contentType != "" {
		request.Header.Set("Content-Type", contentType)
	}
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	return response
}

// encodeOrFatal is used by TestDryRun to parse an object and stop right
// away if it fails.
func encodeOrFatal(t *testing.T, obj runtime.Object) []byte {
	data, err := runtime.Encode(testCodec, obj)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	return data
}

type SimpleRESTStorageWithDeleteCollection struct {
	SimpleRESTStorage
}

// Delete collection doesn't do much, but let us test this path.
func (storage *SimpleRESTStorageWithDeleteCollection) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
	storage.checkContext(ctx)
	return nil, nil
}

func TestDryRunDisabled(t *testing.T) {
	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DryRun, false)()

	tests := []struct {
		path        string
		verb        string
		data        []byte
		contentType string
	}{
		{path: "/namespaces/default/simples", verb: "POST", data: encodeOrFatal(t, &genericapitesting.Simple{Other: "bar"})},
		{path: "/namespaces/default/simples/id", verb: "PUT", data: encodeOrFatal(t, &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "id"}, Other: "bar"})},
		{path: "/namespaces/default/simples/id", verb: "PATCH", data: []byte(`{"labels":{"foo":"bar"}}`), contentType: "application/merge-patch+json; charset=UTF-8"},
		{path: "/namespaces/default/simples/id", verb: "DELETE"},
		{path: "/namespaces/default/simples", verb: "DELETE"},
		{path: "/namespaces/default/simples/id/subsimple", verb: "DELETE"},
	}

	server := httptest.NewServer(handle(map[string]rest.Storage{
		"simples": &SimpleRESTStorageWithDeleteCollection{
			SimpleRESTStorage{
				item: genericapitesting.Simple{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "id",
						Namespace: "",
						UID:       "uid",
					},
					Other: "bar",
				},
			},
		},
		"simples/subsimple": &SimpleXGSubresourceRESTStorage{
			item: genericapitesting.SimpleXGSubresource{
				SubresourceInfo: "foo",
			},
			itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"),
		},
	}))
	defer server.Close()
	for _, test := range tests {
		baseUrl := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version
		response := runRequest(t, baseUrl+test.path, test.verb, test.data, test.contentType)
		if response.StatusCode == http.StatusBadRequest {
			t.Fatalf("unexpected BadRequest: %#v", response)
		}
		response = runRequest(t, baseUrl+test.path+"?dryRun", test.verb, test.data, test.contentType)
		if response.StatusCode != http.StatusBadRequest {
			t.Fatalf("unexpected non BadRequest: %#v", response)
		}
	}
}

func TestDryRunDisabledWithMultiTenancy(t *testing.T) {
	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DryRun, false)()

	tests := []struct {
		path        string
		verb        string
		data        []byte
		contentType string
	}{
		{path: "/tenants/test/namespaces/default/simples", verb: "POST", data: encodeOrFatal(t, &genericapitesting.Simple{Other: "bar"})},
		{path: "/tenants/test/namespaces/default/simples/id", verb: "PUT", data: encodeOrFatal(t, &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "id"}, Other: "bar"})},
		{path: "/tenants/test/namespaces/default/simples/id", verb: "PATCH", data: []byte(`{"labels":{"foo":"bar"}}`), contentType: "application/merge-patch+json; charset=UTF-8"},
		{path: "/tenants/test/namespaces/default/simples/id", verb: "DELETE"},
		{path: "/tenants/test/namespaces/default/simples", verb: "DELETE"},
		{path: "/tenants/test/namespaces/default/simples/id/subsimple", verb: "DELETE"},
	}

	server := httptest.NewServer(handle(map[string]rest.Storage{
		"simples": &SimpleRESTStorageWithDeleteCollection{
			SimpleRESTStorage{
				item: genericapitesting.Simple{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "id",
						Namespace: "",
						UID:       "uid",
					},
					Other: "bar",
				},
			},
		},
		"simples/subsimple": &SimpleXGSubresourceRESTStorage{
			item: genericapitesting.SimpleXGSubresource{
				SubresourceInfo: "foo",
			},
			itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"),
		},
	}))
	defer server.Close()
	for _, test := range tests {
		baseUrl := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version
		response := runRequest(t, baseUrl+test.path, test.verb, test.data, test.contentType)
		if response.StatusCode == http.StatusBadRequest {
			t.Fatalf("unexpected BadRequest: %#v", response)
		}
		response = runRequest(t, baseUrl+test.path+"?dryRun", test.verb, test.data, test.contentType)
		if response.StatusCode != http.StatusBadRequest {
			t.Fatalf("unexpected non BadRequest: %#v", response)
		}
	}
}

type SimpleXGSubresourceRESTStorage struct {
	item    genericapitesting.SimpleXGSubresource
	itemGVK schema.GroupVersionKind
}

var _ = rest.GroupVersionKindProvider(&SimpleXGSubresourceRESTStorage{})

func (storage *SimpleXGSubresourceRESTStorage) New() runtime.Object {
	return &genericapitesting.SimpleXGSubresource{}
}

func (storage *SimpleXGSubresourceRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) {
	return storage.item.DeepCopyObject(), nil
}

func (storage *SimpleXGSubresourceRESTStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
	return nil, true, nil
}

func (storage *SimpleXGSubresourceRESTStorage) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
	return storage.itemGVK
}

func TestXGSubresource(t *testing.T) {
	container := restful.NewContainer()
	container.Router(restful.CurlyRouter{})
	mux := container.ServeMux

	itemID := "theID"
	subresourceStorage := &SimpleXGSubresourceRESTStorage{
		item: genericapitesting.SimpleXGSubresource{
			SubresourceInfo: "foo",
		},
		itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"),
	}
	storage := map[string]rest.Storage{
		"simple":           &SimpleRESTStorage{},
		"simple/subsimple": subresourceStorage,
	}

	group := APIGroupVersion{
		Storage: storage,

		Creater:         scheme,
		Convertor:       scheme,
		UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme),
		Defaulter:       scheme,
		Typer:           scheme,
		Linker:          selfLinker,

		EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(),

		ParameterCodec: parameterCodec,

		Admit: admissionControl,

		Root:                   "/" + prefix,
		GroupVersion:           testGroupVersion,
		OptionsExternalVersion: &testGroupVersion,
		Serializer:             codecs,
	}

	if err := (&group).InstallREST(container); err != nil {
		panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err))
	}

	server := newTestServer(defaultAPIServer{mux, container})
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/subsimple")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("unexpected response: %#v", resp)
	}
	var itemOut genericapitesting.SimpleXGSubresource
	body, err := extractBody(resp, &itemOut)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	// Test if the returned object has the expected group, version and kind
	// We are directly unmarshaling JSON here because TypeMeta cannot be decoded through the
	// installed decoders. TypeMeta cannot be decoded because it is added to the ignored
	// conversion type list in API scheme and hence cannot be converted from input type object
	// to output type object. So it's values don't appear in the decoded output object.
	decoder := json.NewDecoder(strings.NewReader(body))
	var itemFromBody genericapitesting.SimpleXGSubresource
	err = decoder.Decode(&itemFromBody)
	if err != nil {
		t.Errorf("unexpected JSON decoding error: %v", err)
	}
	if want := fmt.Sprintf("%s/%s", testGroup2Version.Group, testGroup2Version.Version); itemFromBody.APIVersion != want {
		t.Errorf("unexpected APIVersion got: %+v want: %+v", itemFromBody.APIVersion, want)
	}
	if itemFromBody.Kind != "SimpleXGSubresource" {
		t.Errorf("unexpected Kind got: %+v want: SimpleXGSubresource", itemFromBody.Kind)
	}

	if itemOut.Name != subresourceStorage.item.Name {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, subresourceStorage.item, string(body))
	}
}

func TestXGSubresourceWithMultiTenancy(t *testing.T) {
	container := restful.NewContainer()
	container.Router(restful.CurlyRouter{})
	mux := container.ServeMux

	itemID := "theID"
	subresourceStorage := &SimpleXGSubresourceRESTStorage{
		item: genericapitesting.SimpleXGSubresource{
			SubresourceInfo: "foo",
		},
		itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"),
	}
	storage := map[string]rest.Storage{
		"simple":           &SimpleRESTStorage{},
		"simple/subsimple": subresourceStorage,
	}

	group := APIGroupVersion{
		Storage: storage,

		Creater:         scheme,
		Convertor:       scheme,
		UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme),
		Defaulter:       scheme,
		Typer:           scheme,
		Linker:          selfLinker,

		EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(),

		ParameterCodec: parameterCodec,

		Admit: admissionControl,

		Root:                   "/" + prefix,
		GroupVersion:           testGroupVersion,
		OptionsExternalVersion: &testGroupVersion,
		Serializer:             codecs,
	}

	if err := (&group).InstallREST(container); err != nil {
		panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err))
	}

	server := newTestServer(defaultAPIServer{mux, container})
	defer server.Close()

	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/tenants/system/namespaces/default/simple/" + itemID + "/subsimple")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("unexpected response: %#v", resp)
	}
	var itemOut genericapitesting.SimpleXGSubresource
	body, err := extractBody(resp, &itemOut)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	// Test if the returned object has the expected group, version and kind
	// We are directly unmarshaling JSON here because TypeMeta cannot be decoded through the
	// installed decoders. TypeMeta cannot be decoded because it is added to the ignored
	// conversion type list in API scheme and hence cannot be converted from input type object
	// to output type object. So it's values don't appear in the decoded output object.
	decoder := json.NewDecoder(strings.NewReader(body))
	var itemFromBody genericapitesting.SimpleXGSubresource
	err = decoder.Decode(&itemFromBody)
	if err != nil {
		t.Errorf("unexpected JSON decoding error: %v", err)
	}
	if want := fmt.Sprintf("%s/%s", testGroup2Version.Group, testGroup2Version.Version); itemFromBody.APIVersion != want {
		t.Errorf("unexpected APIVersion got: %+v want: %+v", itemFromBody.APIVersion, want)
	}
	if itemFromBody.Kind != "SimpleXGSubresource" {
		t.Errorf("unexpected Kind got: %+v want: SimpleXGSubresource", itemFromBody.Kind)
	}

	if itemOut.Name != subresourceStorage.item.Name {
		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, subresourceStorage.item, string(body))
	}
}

func readBodyOrDie(r io.Reader) []byte {
	body, err := ioutil.ReadAll(r)
	if err != nil {
		panic(err)
	}
	return body
}

// BenchmarkUpdateProtobuf measures the cost of processing an update on the server in proto
func BenchmarkUpdateProtobuf(b *testing.B) {
	items := benchmarkItems(b)

	simpleStorage := &SimpleRESTStorage{}
	handler := handle(map[string]rest.Storage{"simples": simpleStorage})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	dest, _ := url.Parse(server.URL)
	dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simples/bar"
	dest.RawQuery = ""

	info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), "application/vnd.kubernetes.protobuf")
	e := codecs.EncoderForVersion(info.Serializer, newGroupVersion)
	data, err := runtime.Encode(e, &items[0])
	if err != nil {
		b.Fatal(err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		request, err := http.NewRequest("PUT", dest.String(), bytes.NewReader(data))
		if err != nil {
			b.Fatalf("unexpected error: %v", err)
		}
		request.Header.Set("Accept", "application/vnd.kubernetes.protobuf")
		request.Header.Set("Content-Type", "application/vnd.kubernetes.protobuf")
		response, err := client.Do(request)
		if err != nil {
			b.Fatalf("unexpected error: %v", err)
		}
		if response.StatusCode != http.StatusBadRequest {
			body, _ := ioutil.ReadAll(response.Body)
			b.Fatalf("Unexpected response %#v\n%s", response, body)
		}
		_, _ = ioutil.ReadAll(response.Body)
		response.Body.Close()
	}
	b.StopTimer()
}

// BenchmarkUpdateProtobuf measures the cost of processing an update on the server in proto
func BenchmarkUpdateProtobufWithMultiTenancy(b *testing.B) {
	items := benchmarkItems(b)

	simpleStorage := &SimpleRESTStorage{}
	handler := handle(map[string]rest.Storage{"simples": simpleStorage})
	server := httptest.NewServer(handler)
	defer server.Close()
	client := http.Client{}

	dest, _ := url.Parse(server.URL)
	dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/tenants/test/namespaces/foo/simples/bar"
	dest.RawQuery = ""

	info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), "application/vnd.kubernetes.protobuf")
	e := codecs.EncoderForVersion(info.Serializer, newGroupVersion)
	data, err := runtime.Encode(e, &items[0])
	if err != nil {
		b.Fatal(err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		request, err := http.NewRequest("PUT", dest.String(), bytes.NewReader(data))
		if err != nil {
			b.Fatalf("unexpected error: %v", err)
		}
		request.Header.Set("Accept", "application/vnd.kubernetes.protobuf")
		request.Header.Set("Content-Type", "application/vnd.kubernetes.protobuf")
		response, err := client.Do(request)
		if err != nil {
			b.Fatalf("unexpected error: %v", err)
		}
		if response.StatusCode != http.StatusBadRequest {
			body, _ := ioutil.ReadAll(response.Body)
			b.Fatalf("Unexpected response %#v\n%s", response, body)
		}
		_, _ = ioutil.ReadAll(response.Body)
		response.Body.Close()
	}
	b.StopTimer()
}

func newTestServer(handler http.Handler) *httptest.Server {
	handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver())
	return httptest.NewServer(handler)
}

func newTestRequestInfoResolver() *request.RequestInfoFactory {
	return &request.RequestInfoFactory{
		APIPrefixes:          sets.NewString("api", "apis"),
		GrouplessAPIPrefixes: sets.NewString("api"),
	}
}

const benchmarkSeed = 100

func benchmarkItems(b *testing.B) []example.Pod {
	clientapiObjectFuzzer := fuzzer.FuzzerFor(examplefuzzer.Funcs, rand.NewSource(benchmarkSeed), codecs)
	items := make([]example.Pod, 3)
	for i := range items {
		clientapiObjectFuzzer.Fuzz(&items[i])
	}
	return items
}
