Add unit test framework for edit command

pull/6/head
Jordan Liggitt 2017-02-03 13:51:24 -05:00
parent 67859efaec
commit af3505f53c
No known key found for this signature in database
GPG Key ID: 24E7ADF9A3B42012
10 changed files with 667 additions and 7 deletions

View File

@ -149,6 +149,7 @@ go_test(
"delete_test.go",
"describe_test.go",
"drain_test.go",
"edit_test.go",
"exec_test.go",
"expose_test.go",
"get_test.go",
@ -165,6 +166,7 @@ go_test(
"top_test.go",
],
data = [
"testdata",
"//examples:config",
"//test/fixtures",
],
@ -190,6 +192,7 @@ go_test(
"//pkg/util/term:go_default_library",
"//vendor:github.com/spf13/cobra",
"//vendor:github.com/stretchr/testify/assert",
"//vendor:gopkg.in/yaml.v2",
"//vendor:k8s.io/apimachinery/pkg/api/equality",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
@ -200,7 +203,9 @@ go_test(
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/json",
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/streaming",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apimachinery/pkg/util/intstr",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/strategicpatch",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/watch",
@ -227,6 +232,7 @@ filegroup(
"//pkg/kubectl/cmd/rollout:all-srcs",
"//pkg/kubectl/cmd/set:all-srcs",
"//pkg/kubectl/cmd/templates:all-srcs",
"//pkg/kubectl/cmd/testdata/edit:all-srcs",
"//pkg/kubectl/cmd/testing:all-srcs",
"//pkg/kubectl/cmd/util:all-srcs",
],

View File

@ -0,0 +1,281 @@
/*
Copyright 2017 The Kubernetes Authors.
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 cmd
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/spf13/cobra"
yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
type EditTestCase struct {
Description string `yaml:"description"`
// create or edit
Mode string `yaml:"mode"`
Args []string `yaml:"args"`
Filename string `yaml:"filename"`
Output string `yaml:"outputFormat"`
Namespace string `yaml:"namespace"`
ExpectedStdout []string `yaml:"expectedStdout"`
ExpectedStderr []string `yaml:"expectedStderr"`
ExpectedExitCode int `yaml:"expectedExitCode"`
Steps []EditStep `yaml:"steps"`
}
type EditStep struct {
// edit or request
StepType string `yaml:"type"`
// only applies to request
RequestMethod string `yaml:"expectedMethod,omitempty"`
RequestPath string `yaml:"expectedPath,omitempty"`
RequestContentType string `yaml:"expectedContentType,omitempty"`
Input string `yaml:"expectedInput"`
// only applies to request
ResponseStatusCode int `yaml:"resultingStatusCode,omitempty"`
Output string `yaml:"resultingOutput"`
}
func TestEdit(t *testing.T) {
var (
name string
testcase EditTestCase
i int
err error
)
const updateEnvVar = "UPDATE_EDIT_FIXTURE_DATA"
updateInputFixtures := os.Getenv(updateEnvVar) == "true"
reqResp := func(req *http.Request) (*http.Response, error) {
defer func() { i++ }()
if i > len(testcase.Steps)-1 {
t.Fatalf("%s, step %d: more requests than steps, got %s %s", name, i, req.Method, req.URL.Path)
}
step := testcase.Steps[i]
body := []byte{}
if req.Body != nil {
body, err = ioutil.ReadAll(req.Body)
if err != nil {
t.Fatalf("%s, step %d: %v", name, i, err)
}
}
inputFile := filepath.Join("testdata/edit", "testcase-"+name, step.Input)
expectedInput, err := ioutil.ReadFile(inputFile)
if err != nil {
t.Fatalf("%s, step %d: %v", name, i, err)
}
outputFile := filepath.Join("testdata/edit", "testcase-"+name, step.Output)
resultingOutput, err := ioutil.ReadFile(outputFile)
if err != nil {
t.Fatalf("%s, step %d: %v", name, i, err)
}
if req.Method == "POST" && req.URL.Path == "/callback" {
if step.StepType != "edit" {
t.Fatalf("%s, step %d: expected edit step, got %s %s", name, i, req.Method, req.URL.Path)
}
if bytes.Compare(body, expectedInput) != 0 {
if updateInputFixtures {
// Convenience to allow recapturing the input and persisting it here
ioutil.WriteFile(inputFile, body, os.FileMode(0644))
} else {
t.Errorf("%s, step %d: diff in edit content:\n%s", name, i, diff.StringDiff(string(body), string(expectedInput)))
t.Logf("If the change in input is expected, rerun tests with %s=true to update input fixtures", updateEnvVar)
}
}
return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(resultingOutput))}, nil
} else {
if step.StepType != "request" {
t.Fatalf("%s, step %d: expected request step, got %s %s", name, i, req.Method, req.URL.Path)
}
body = tryIndent(body)
expectedInput = tryIndent(expectedInput)
if req.Method != step.RequestMethod || req.URL.Path != step.RequestPath || req.Header.Get("Content-Type") != step.RequestContentType {
t.Fatalf(
"%s, step %d: expected \n%s %s (content-type=%s)\ngot\n%s %s (content-type=%s)", name, i,
step.RequestMethod, step.RequestPath, step.RequestContentType,
req.Method, req.URL.Path, req.Header.Get("Content-Type"),
)
}
if bytes.Compare(body, expectedInput) != 0 {
if updateInputFixtures {
// Convenience to allow recapturing the input and persisting it here
ioutil.WriteFile(inputFile, body, os.FileMode(0644))
} else {
t.Errorf("%s, step %d: diff in edit content:\n%s", name, i, diff.StringDiff(string(body), string(expectedInput)))
t.Logf("If the change in input is expected, rerun tests with %s=true to update input fixtures", updateEnvVar)
}
}
return &http.Response{StatusCode: step.ResponseStatusCode, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(resultingOutput))}, nil
}
}
handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
resp, _ := reqResp(req)
for k, vs := range resp.Header {
w.Header().Del(k)
for _, v := range vs {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
})
server := httptest.NewServer(handler)
defer server.Close()
os.Setenv("KUBE_EDITOR", "testdata/edit/test_editor.sh")
os.Setenv("KUBE_EDITOR_CALLBACK", server.URL+"/callback")
testcases := sets.NewString()
filepath.Walk("testdata/edit", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == "testdata/edit" {
return nil
}
name := filepath.Base(path)
if info.IsDir() {
if strings.HasPrefix(name, "testcase-") {
testcases.Insert(strings.TrimPrefix(name, "testcase-"))
}
return filepath.SkipDir
}
return nil
})
// sanity check that we found the right folder
if !testcases.Has("create-list") {
t.Fatalf("Error locating edit testcases")
}
for _, testcaseName := range testcases.List() {
t.Logf("Running testcase: %s", testcaseName)
i = 0
name = testcaseName
testcase = EditTestCase{}
testcaseDir := filepath.Join("testdata", "edit", "testcase-"+name)
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
if err != nil {
t.Fatalf("%s: %v", name, err)
}
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
t.Fatalf("%s: %v", name, err)
}
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.ClientForMappingFunc = func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
versionedAPIPath := ""
if mapping.GroupVersionKind.Group == "" {
versionedAPIPath = "/api/" + mapping.GroupVersionKind.Version
} else {
versionedAPIPath = "/apis/" + mapping.GroupVersionKind.Group + "/" + mapping.GroupVersionKind.Version
}
return &fake.RESTClient{
APIRegistry: api.Registry,
VersionedAPIPath: versionedAPIPath,
NegotiatedSerializer: ns, //unstructuredSerializer,
Client: fake.CreateHTTPClient(reqResp),
}, nil
}
if len(testcase.Namespace) > 0 {
tf.Namespace = testcase.Namespace
}
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
var cmd *cobra.Command
switch testcase.Mode {
case "edit":
cmd = NewCmdEdit(f, buf, errBuf)
case "create":
cmd = NewCmdCreate(f, buf, errBuf)
cmd.Flags().Set("edit", "true")
default:
t.Errorf("%s: unexpected mode %s", name, testcase.Mode)
continue
}
if len(testcase.Filename) > 0 {
cmd.Flags().Set("filename", filepath.Join(testcaseDir, testcase.Filename))
}
if len(testcase.Output) > 0 {
cmd.Flags().Set("output", testcase.Output)
}
cmdutil.BehaviorOnFatal(func(str string, code int) {
errBuf.WriteString(str)
if testcase.ExpectedExitCode != code {
t.Errorf("%s: expected exit code %d, got %d: %s", name, testcase.ExpectedExitCode, code, str)
}
})
cmd.Run(cmd, testcase.Args)
stdout := buf.String()
stderr := errBuf.String()
for _, s := range testcase.ExpectedStdout {
if !strings.Contains(stdout, s) {
t.Errorf("%s: expected to see '%s' in stdout\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr)
}
}
for _, s := range testcase.ExpectedStderr {
if !strings.Contains(stderr, s) {
t.Errorf("%s: expected to see '%s' in stderr\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr)
}
}
}
}
func tryIndent(data []byte) []byte {
indented := &bytes.Buffer{}
if err := json.Indent(indented, data, "", "\t"); err == nil {
return indented.Bytes()
}
return data
}

35
pkg/kubectl/cmd/testdata/edit/BUILD vendored Normal file
View File

@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "edit",
library = ":go_default_library",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["record.go"],
tags = ["automanaged"],
deps = ["//vendor:gopkg.in/yaml.v2"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

22
pkg/kubectl/cmd/testdata/edit/README vendored Normal file
View File

@ -0,0 +1,22 @@
This folder contains test cases for interactive edit, and helpers for recording new test cases
To record a new test:
1. Start a local cluster running unsecured on http://localhost:8080 (e.g. hack/local-up-cluster.sh)
2. Set up any pre-existing resources you want to be available on that server (namespaces, resources to edit, etc)
3. Run ./pkg/kubectl/cmd/testdata/edit/record_testcase.sh my-testcase
4. Run the desired `kubectl edit ...` command, and interact with the editor as desired until it completes.
* You can do things that cause errors to appear in the editor (change immutable fields, fail validation, etc)
* You can perform edit flows that invoke the editor multiple times
* You can make out-of-band changes to the server resources that cause conflict errors to be returned
* The API requests/responses and editor inputs/outputs are captured in your testcase folder
5. Type exit.
6. Inspect the captured requests/responses and inputs/outputs for sanity
7. Modify the generated test.yaml file:
* Set a description of what the test is doing
* Enter the args (if any) you invoked edit with
* Enter the filename (if any) you invoked edit with
* Enter the output format (if any) you invoked edit with
* Optionally specify substrings to look for in the stdout or stderr of the edit command
8. Add your new testcase name to the list of testcases in edit_test.go
9. Run `go test ./pkg/kubectl/cmd -run TestEdit -v` to run edit tests

169
pkg/kubectl/cmd/testdata/edit/record.go vendored Normal file
View File

@ -0,0 +1,169 @@
/*
Copyright 2017 The Kubernetes Authors.
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 main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
yaml "gopkg.in/yaml.v2"
)
type EditTestCase struct {
Description string `yaml:"description"`
// create or edit
Mode string `yaml:"mode"`
Args []string `yaml:"args"`
Filename string `yaml:"filename"`
Output string `yaml:"outputFormat"`
Namespace string `yaml:"namespace"`
ExpectedStdout []string `yaml:"expectedStdout"`
ExpectedStderr []string `yaml:"expectedStderr"`
ExpectedExitCode int `yaml:"expectedExitCode"`
Steps []EditStep `yaml:"steps"`
}
type EditStep struct {
// edit or request
StepType string `yaml:"type"`
// only applies to request
RequestMethod string `yaml:"expectedMethod,omitempty"`
RequestPath string `yaml:"expectedPath,omitempty"`
RequestContentType string `yaml:"expectedContentType,omitempty"`
Input string `yaml:"expectedInput"`
// only applies to request
ResponseStatusCode int `yaml:"resultingStatusCode,omitempty"`
Output string `yaml:"resultingOutput"`
}
func main() {
tc := &EditTestCase{
Description: "add a testcase description",
Mode: "edit",
Args: []string{"set", "args"},
ExpectedStdout: []string{"expected stdout substring"},
ExpectedStderr: []string{"expected stderr substring"},
}
var currentStep *EditStep
fmt.Println(http.ListenAndServe(":8081", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Record non-discovery things
record := false
switch segments := strings.Split(strings.Trim(req.URL.Path, "/"), "/"); segments[0] {
case "api":
// api, version
record = len(segments) > 2
case "apis":
// apis, group, version
record = len(segments) > 3
case "callback":
record = true
}
body, err := ioutil.ReadAll(req.Body)
checkErr(err)
switch m, p := req.Method, req.URL.Path; {
case m == "POST" && p == "/callback/in":
if currentStep != nil {
panic("cannot post input with step already in progress")
}
filename := fmt.Sprintf("%d.original", len(tc.Steps))
checkErr(ioutil.WriteFile(filename, body, os.FileMode(0755)))
currentStep = &EditStep{StepType: "edit", Input: filename}
case m == "POST" && p == "/callback/out":
if currentStep == nil || currentStep.StepType != "edit" {
panic("cannot post output without posting input first")
}
filename := fmt.Sprintf("%d.edited", len(tc.Steps))
checkErr(ioutil.WriteFile(filename, body, os.FileMode(0755)))
currentStep.Output = filename
tc.Steps = append(tc.Steps, *currentStep)
currentStep = nil
default:
if currentStep != nil {
panic("cannot make request with step already in progress")
}
urlCopy := *req.URL
urlCopy.Host = "localhost:8080"
urlCopy.Scheme = "http"
proxiedReq, err := http.NewRequest(req.Method, urlCopy.String(), bytes.NewReader(body))
checkErr(err)
proxiedReq.Header = req.Header
resp, err := http.DefaultClient.Do(proxiedReq)
checkErr(err)
defer resp.Body.Close()
bodyOut, err := ioutil.ReadAll(resp.Body)
checkErr(err)
for k, vs := range resp.Header {
for _, v := range vs {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.StatusCode)
w.Write(bodyOut)
if record {
infile := fmt.Sprintf("%d.request", len(tc.Steps))
outfile := fmt.Sprintf("%d.response", len(tc.Steps))
checkErr(ioutil.WriteFile(infile, tryIndent(body), os.FileMode(0755)))
checkErr(ioutil.WriteFile(outfile, tryIndent(bodyOut), os.FileMode(0755)))
tc.Steps = append(tc.Steps, EditStep{
StepType: "request",
Input: infile,
Output: outfile,
RequestContentType: req.Header.Get("Content-Type"),
RequestMethod: req.Method,
RequestPath: req.URL.Path,
ResponseStatusCode: resp.StatusCode,
})
}
}
tcData, err := yaml.Marshal(tc)
checkErr(err)
checkErr(ioutil.WriteFile("test.yaml", tcData, os.FileMode(0755)))
})))
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
func tryIndent(data []byte) []byte {
indented := &bytes.Buffer{}
if err := json.Indent(indented, data, "", "\t"); err == nil {
return indented.Bytes()
}
return data
}

View File

@ -0,0 +1,26 @@
#!/bin/bash
# Copyright 2017 The Kubernetes Authors.
#
# 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.
set -o errexit
set -o nounset
set -o pipefail
# send the original content to the server
curl -s -k -XPOST "http://localhost:8081/callback/in" --data-binary "@${1}"
# allow the user to edit the file
vi "${1}"
# send the resulting content to the server
curl -s -k -XPOST "http://localhost:8081/callback/out" --data-binary "@${1}"

View File

@ -0,0 +1,70 @@
#!/bin/bash
# Copyright 2017 The Kubernetes Authors.
#
# 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.
set -o errexit
set -o nounset
set -o pipefail
if [[ -z "${1-}" ]]; then
echo "Usage: record_testcase.sh testcase-name"
exit 1
fi
# Clean up the test server
function cleanup {
if [[ ! -z "${pid-}" ]]; then
echo "Stopping recording server (${pid})"
# kill the process `go run` launched
pkill -P "${pid}"
# kill the `go run` process itself
kill -9 "${pid}"
fi
}
testcase="${1}"
test_root="$(dirname "${BASH_SOURCE}")"
testcase_dir="${test_root}/testcase-${testcase}"
mkdir -p "${testcase_dir}"
pushd "${testcase_dir}"
export EDITOR="../record_editor.sh"
go run "../record.go" &
pid=$!
trap cleanup EXIT
echo "Started recording server (${pid})"
# Make a kubeconfig that makes kubectl talk to our test server
edit_kubeconfig="${TMP:-/tmp}/edit_test.kubeconfig"
echo "apiVersion: v1
clusters:
- cluster:
server: http://localhost:8081
name: test
contexts:
- context:
cluster: test
user: test
name: test
current-context: test
kind: Config
users: []
" > "${edit_kubeconfig}"
export KUBECONFIG="${edit_kubeconfig}"
echo "Starting subshell. Type exit when finished."
bash
popd

32
pkg/kubectl/cmd/testdata/edit/test_editor.sh vendored Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
# Copyright 2017 The Kubernetes Authors.
#
# 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.
set -o errexit
set -o nounset
set -o pipefail
# Send the file content to the server
if command -v curl &>/dev/null; then
curl -s -k -XPOST --data-binary "@${1}" -o "${1}.result" "${KUBE_EDITOR_CALLBACK}"
elif command -v wget &>/dev/null; then
wget --post-file="${1}" -O "${1}.result" "${KUBE_EDITOR_CALLBACK}"
else
echo "curl and wget are unavailable" >&2
exit 1
fi
# Use the response as the edited version
mv "${1}.result" "${1}"

View File

@ -216,6 +216,9 @@ type TestFactory struct {
Namespace string
ClientConfig *restclient.Config
Err error
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
}
type FakeFactory struct {
@ -294,7 +297,10 @@ func (f *FakeFactory) BareClientConfig() (*restclient.Config, error) {
return f.tf.ClientConfig, f.tf.Err
}
func (f *FakeFactory) ClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
func (f *FakeFactory) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
if f.tf.ClientForMappingFunc != nil {
return f.tf.ClientForMappingFunc(mapping)
}
return f.tf.Client, f.tf.Err
}
@ -311,7 +317,10 @@ func (f *FakeFactory) ClientConfigForVersion(requiredVersion *schema.GroupVersio
return nil, nil
}
func (f *FakeFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
func (f *FakeFactory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
if f.tf.UnstructuredClientForMappingFunc != nil {
return f.tf.UnstructuredClientForMappingFunc(mapping)
}
return f.tf.UnstructuredClient, f.tf.Err
}
@ -481,6 +490,9 @@ func (f *fakeMixedFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTC
if m.ObjectConvertor == api.Scheme {
return f.apiClient, f.tf.Err
}
if f.tf.ClientForMappingFunc != nil {
return f.tf.ClientForMappingFunc(m)
}
return f.tf.Client, f.tf.Err
}
@ -553,11 +565,17 @@ func (f *fakeAPIFactory) ClientConfig() (*restclient.Config, error) {
return f.tf.ClientConfig, f.tf.Err
}
func (f *fakeAPIFactory) ClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
func (f *fakeAPIFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTClient, error) {
if f.tf.ClientForMappingFunc != nil {
return f.tf.ClientForMappingFunc(m)
}
return f.tf.Client, f.tf.Err
}
func (f *fakeAPIFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
func (f *fakeAPIFactory) UnstructuredClientForMapping(m *meta.RESTMapping) (resource.RESTClient, error) {
if f.tf.UnstructuredClientForMappingFunc != nil {
return f.tf.UnstructuredClientForMappingFunc(m)
}
return f.tf.UnstructuredClient, f.tf.Err
}

View File

@ -48,6 +48,7 @@ type RESTClient struct {
NegotiatedSerializer runtime.NegotiatedSerializer
GroupName string
APIRegistry *registered.APIRegistrationManager
VersionedAPIPath string
Req *http.Request
Resp *http.Response
@ -62,8 +63,8 @@ func (c *RESTClient) Put() *restclient.Request {
return c.request("PUT")
}
func (c *RESTClient) Patch(_ types.PatchType) *restclient.Request {
return c.request("PATCH")
func (c *RESTClient) Patch(pt types.PatchType) *restclient.Request {
return c.request("PATCH").SetHeader("Content-Type", string(pt))
}
func (c *RESTClient) Post() *restclient.Request {
@ -110,7 +111,7 @@ func (c *RESTClient) request(verb string) *restclient.Request {
serializers.StreamingSerializer = info.StreamSerializer.Serializer
serializers.Framer = info.StreamSerializer.Framer
}
return restclient.NewRequest(c, verb, &url.URL{Host: "localhost"}, "", config, serializers, nil, nil)
return restclient.NewRequest(c, verb, &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil)
}
func (c *RESTClient) Do(req *http.Request) (*http.Response, error) {