...

Source file src/k8s.io/kubectl/pkg/cmd/delete/delete_test.go

Documentation: k8s.io/kubectl/pkg/cmd/delete

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package delete
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"strconv"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/spf13/cobra"
    29  
    30  	corev1 "k8s.io/api/core/v1"
    31  	"k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/cli-runtime/pkg/genericiooptions"
    34  	"k8s.io/cli-runtime/pkg/resource"
    35  	"k8s.io/client-go/rest/fake"
    36  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    37  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    38  	"k8s.io/kubectl/pkg/scheme"
    39  	"k8s.io/utils/pointer"
    40  )
    41  
    42  func fakecmd() *cobra.Command {
    43  	cmd := &cobra.Command{
    44  		Use:                   "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
    45  		DisableFlagsInUseLine: true,
    46  		Run:                   func(cmd *cobra.Command, args []string) {},
    47  	}
    48  	cmdutil.AddDryRunFlag(cmd)
    49  	return cmd
    50  }
    51  
    52  func TestDeleteFlagValidation(t *testing.T) {
    53  	f := cmdtesting.NewTestFactory()
    54  	defer f.Cleanup()
    55  
    56  	tests := []struct {
    57  		flags       DeleteFlags
    58  		args        [][]string
    59  		expectedErr string
    60  	}{
    61  		{
    62  			flags: DeleteFlags{
    63  				Raw:         pointer.String("test"),
    64  				Interactive: pointer.Bool(true),
    65  			},
    66  			expectedErr: "--interactive can not be used with --raw",
    67  		},
    68  	}
    69  
    70  	for _, test := range tests {
    71  		cmd := fakecmd()
    72  		deleteOptions, err := test.flags.ToOptions(nil, genericiooptions.NewTestIOStreamsDiscard())
    73  		if err != nil {
    74  			t.Fatalf("unexpected error creating delete options: %s", err)
    75  		}
    76  		deleteOptions.Filenames = []string{"../../../testdata/redis-master-controller.yaml"}
    77  		err = deleteOptions.Complete(f, nil, cmd)
    78  		if err != nil {
    79  			t.Fatalf("unexpected error creating delete options: %s", err)
    80  		}
    81  		err = deleteOptions.Validate()
    82  		if err == nil {
    83  			t.Fatalf("missing expected error")
    84  		}
    85  		if test.expectedErr != err.Error() {
    86  			t.Errorf("expected error %s, got %s", test.expectedErr, err)
    87  		}
    88  	}
    89  }
    90  
    91  func TestDeleteObjectByTuple(t *testing.T) {
    92  	cmdtesting.InitTestErrorHandler(t)
    93  	_, _, rc := cmdtesting.TestData()
    94  
    95  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
    96  	defer tf.Cleanup()
    97  
    98  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
    99  
   100  	tf.UnstructuredClient = &fake.RESTClient{
   101  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   102  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   103  			switch p, m := req.URL.Path, req.Method; {
   104  
   105  			// replication controller with cascade off
   106  			case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
   107  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   108  
   109  			// secret with cascade on, but no client-side reaper
   110  			case p == "/namespaces/test/secrets/mysecret" && m == "DELETE":
   111  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   112  
   113  			default:
   114  				// Ensures no GET is performed when deleting by name
   115  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   116  				return nil, nil
   117  			}
   118  		}),
   119  	}
   120  
   121  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   122  	cmd := NewCmdDelete(tf, streams)
   123  	cmd.Flags().Set("namespace", "test")
   124  	cmd.Flags().Set("cascade", "false")
   125  	cmd.Flags().Set("output", "name")
   126  	cmd.Run(cmd, []string{"replicationcontrollers/redis-master-controller"})
   127  	if buf.String() != "replicationcontroller/redis-master-controller\n" {
   128  		t.Errorf("unexpected output: %s", buf.String())
   129  	}
   130  
   131  	// Test cascading delete of object without client-side reaper doesn't make GET requests
   132  	streams, _, buf, _ = genericiooptions.NewTestIOStreams()
   133  	cmd = NewCmdDelete(tf, streams)
   134  	cmd.Flags().Set("namespace", "test")
   135  	cmd.Flags().Set("output", "name")
   136  	cmd.Run(cmd, []string{"secrets/mysecret"})
   137  	if buf.String() != "secret/mysecret\n" {
   138  		t.Errorf("unexpected output: %s", buf.String())
   139  	}
   140  }
   141  
   142  func hasExpectedPropagationPolicy(body io.ReadCloser, policy *metav1.DeletionPropagation) bool {
   143  	if body == nil || policy == nil {
   144  		return body == nil && policy == nil
   145  	}
   146  	var parsedBody metav1.DeleteOptions
   147  	rawBody, _ := io.ReadAll(body)
   148  	json.Unmarshal(rawBody, &parsedBody)
   149  	if parsedBody.PropagationPolicy == nil {
   150  		return false
   151  	}
   152  	return *policy == *parsedBody.PropagationPolicy
   153  }
   154  
   155  // TestCascadingStrategy tests that DeleteOptions.DeletionPropagation is appropriately set while deleting objects.
   156  func TestCascadingStrategy(t *testing.T) {
   157  	cmdtesting.InitTestErrorHandler(t)
   158  	_, _, rc := cmdtesting.TestData()
   159  
   160  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   161  	defer tf.Cleanup()
   162  
   163  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   164  
   165  	var policy *metav1.DeletionPropagation
   166  	tf.UnstructuredClient = &fake.RESTClient{
   167  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   168  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   169  			switch p, m, b := req.URL.Path, req.Method, req.Body; {
   170  
   171  			case p == "/namespaces/test/secrets/mysecret" && m == "DELETE" && hasExpectedPropagationPolicy(b, policy):
   172  
   173  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   174  			default:
   175  				return nil, nil
   176  			}
   177  		}),
   178  	}
   179  
   180  	// DeleteOptions.PropagationPolicy should be Background, when cascading strategy is empty (default).
   181  	backgroundPolicy := metav1.DeletePropagationBackground
   182  	policy = &backgroundPolicy
   183  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   184  	cmd := NewCmdDelete(tf, streams)
   185  	cmd.Flags().Set("namespace", "test")
   186  	cmd.Flags().Set("output", "name")
   187  	cmd.Run(cmd, []string{"secrets/mysecret"})
   188  	if buf.String() != "secret/mysecret\n" {
   189  		t.Errorf("unexpected output: %s", buf.String())
   190  	}
   191  
   192  	// DeleteOptions.PropagationPolicy should be Foreground, when cascading strategy is foreground.
   193  	foregroundPolicy := metav1.DeletePropagationForeground
   194  	policy = &foregroundPolicy
   195  	streams, _, buf, _ = genericiooptions.NewTestIOStreams()
   196  	cmd = NewCmdDelete(tf, streams)
   197  	cmd.Flags().Set("namespace", "test")
   198  	cmd.Flags().Set("cascade", "foreground")
   199  	cmd.Flags().Set("output", "name")
   200  	cmd.Run(cmd, []string{"secrets/mysecret"})
   201  	if buf.String() != "secret/mysecret\n" {
   202  		t.Errorf("unexpected output: %s", buf.String())
   203  	}
   204  
   205  	// Test that delete options should be set to orphan when cascading strategy is orphan.
   206  	orphanPolicy := metav1.DeletePropagationOrphan
   207  	policy = &orphanPolicy
   208  	streams, _, buf, _ = genericiooptions.NewTestIOStreams()
   209  	cmd = NewCmdDelete(tf, streams)
   210  	cmd.Flags().Set("namespace", "test")
   211  	cmd.Flags().Set("cascade", "orphan")
   212  	cmd.Flags().Set("output", "name")
   213  	cmd.Run(cmd, []string{"secrets/mysecret"})
   214  	if buf.String() != "secret/mysecret\n" {
   215  		t.Errorf("unexpected output: %s", buf.String())
   216  	}
   217  }
   218  
   219  func TestDeleteNamedObject(t *testing.T) {
   220  	cmdtesting.InitTestErrorHandler(t)
   221  	cmdtesting.InitTestErrorHandler(t)
   222  	_, _, rc := cmdtesting.TestData()
   223  
   224  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   225  	defer tf.Cleanup()
   226  
   227  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   228  
   229  	tf.UnstructuredClient = &fake.RESTClient{
   230  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   231  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   232  			switch p, m := req.URL.Path, req.Method; {
   233  
   234  			// replication controller with cascade off
   235  			case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
   236  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   237  
   238  			// secret with cascade on, but no client-side reaper
   239  			case p == "/namespaces/test/secrets/mysecret" && m == "DELETE":
   240  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   241  
   242  			default:
   243  				// Ensures no GET is performed when deleting by name
   244  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   245  				return nil, nil
   246  			}
   247  		}),
   248  	}
   249  
   250  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   251  	cmd := NewCmdDelete(tf, streams)
   252  	cmd.Flags().Set("namespace", "test")
   253  	cmd.Flags().Set("cascade", "false")
   254  	cmd.Flags().Set("output", "name")
   255  	cmd.Run(cmd, []string{"replicationcontrollers", "redis-master-controller"})
   256  	if buf.String() != "replicationcontroller/redis-master-controller\n" {
   257  		t.Errorf("unexpected output: %s", buf.String())
   258  	}
   259  
   260  	// Test cascading delete of object without client-side reaper doesn't make GET requests
   261  	streams, _, buf, _ = genericiooptions.NewTestIOStreams()
   262  	cmd = NewCmdDelete(tf, streams)
   263  	cmd.Flags().Set("namespace", "test")
   264  	cmd.Flags().Set("cascade", "false")
   265  	cmd.Flags().Set("output", "name")
   266  	cmd.Run(cmd, []string{"secrets", "mysecret"})
   267  	if buf.String() != "secret/mysecret\n" {
   268  		t.Errorf("unexpected output: %s", buf.String())
   269  	}
   270  }
   271  
   272  func TestDeleteObject(t *testing.T) {
   273  	cmdtesting.InitTestErrorHandler(t)
   274  	_, _, rc := cmdtesting.TestData()
   275  
   276  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   277  	defer tf.Cleanup()
   278  
   279  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   280  
   281  	tf.UnstructuredClient = &fake.RESTClient{
   282  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   283  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   284  			switch p, m := req.URL.Path, req.Method; {
   285  			case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
   286  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   287  			default:
   288  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   289  				return nil, nil
   290  			}
   291  		}),
   292  	}
   293  
   294  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   295  	cmd := NewCmdDelete(tf, streams)
   296  	cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
   297  	cmd.Flags().Set("cascade", "false")
   298  	cmd.Flags().Set("output", "name")
   299  	cmd.Run(cmd, []string{})
   300  
   301  	// uses the name from the file, not the response
   302  	if buf.String() != "replicationcontroller/redis-master\n" {
   303  		t.Errorf("unexpected output: %s", buf.String())
   304  	}
   305  }
   306  
   307  func TestPreviewResultEqualToResult(t *testing.T) {
   308  	deleteFlags := NewDeleteCommandFlags("")
   309  	deleteFlags.Interactive = pointer.Bool(true)
   310  
   311  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   312  	defer tf.Cleanup()
   313  
   314  	streams, _, _, _ := genericiooptions.NewTestIOStreams()
   315  
   316  	deleteOptions, err := deleteFlags.ToOptions(nil, streams)
   317  	deleteOptions.Filenames = []string{"../../../testdata/redis-master-controller.yaml"}
   318  	if err != nil {
   319  		t.Errorf("unexpected error %v", err)
   320  	}
   321  	err = deleteOptions.Complete(tf, nil, fakecmd())
   322  	if err != nil {
   323  		t.Errorf("unexpected error %v", err)
   324  	}
   325  
   326  	infos, err := deleteOptions.Result.Infos()
   327  	if err != nil {
   328  		t.Errorf("unexpected error %v", err)
   329  	}
   330  	previewInfos, err := deleteOptions.PreviewResult.Infos()
   331  	if err != nil {
   332  		t.Errorf("unexpected error %v", err)
   333  	}
   334  	if len(infos) != len(previewInfos) {
   335  		t.Errorf("result and previewResult must match")
   336  	}
   337  }
   338  
   339  func TestDeleteObjectWithInteractive(t *testing.T) {
   340  	cmdtesting.InitTestErrorHandler(t)
   341  	_, _, rc := cmdtesting.TestData()
   342  
   343  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   344  	defer tf.Cleanup()
   345  
   346  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   347  
   348  	tf.UnstructuredClient = &fake.RESTClient{
   349  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   350  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   351  			switch p, m := req.URL.Path, req.Method; {
   352  			case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
   353  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   354  			default:
   355  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   356  				return nil, nil
   357  			}
   358  		}),
   359  	}
   360  
   361  	streams, in, buf, _ := genericiooptions.NewTestIOStreams()
   362  	fmt.Fprint(in, "y")
   363  	cmd := NewCmdDelete(tf, streams)
   364  	err := cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
   365  	if err != nil {
   366  		t.Errorf("unexpected error %v", err)
   367  	}
   368  	err = cmd.Flags().Set("output", "name")
   369  	if err != nil {
   370  		t.Errorf("unexpected error %v", err)
   371  	}
   372  	err = cmd.Flags().Set("interactive", "true")
   373  	if err != nil {
   374  		t.Errorf("unexpected error %v", err)
   375  	}
   376  	cmd.Run(cmd, []string{})
   377  
   378  	if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/n): replicationcontroller/redis-master\n" {
   379  		t.Errorf("unexpected output: %s", buf.String())
   380  	}
   381  
   382  	streams, in, buf, _ = genericiooptions.NewTestIOStreams()
   383  	fmt.Fprint(in, "n")
   384  	cmd = NewCmdDelete(tf, streams)
   385  	err = cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
   386  	if err != nil {
   387  		t.Errorf("unexpected error %v", err)
   388  	}
   389  	err = cmd.Flags().Set("output", "name")
   390  	if err != nil {
   391  		t.Errorf("unexpected error %v", err)
   392  	}
   393  	err = cmd.Flags().Set("interactive", "true")
   394  	if err != nil {
   395  		t.Errorf("unexpected error %v", err)
   396  	}
   397  	cmd.Run(cmd, []string{})
   398  
   399  	if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/n): deletion is cancelled\n" {
   400  		t.Errorf("unexpected output: %s", buf.String())
   401  	}
   402  	if buf.String() == ": replicationcontroller/redis-master\n" {
   403  		t.Errorf("unexpected output: %s", buf.String())
   404  	}
   405  }
   406  
   407  func TestGracePeriodScenarios(t *testing.T) {
   408  	pods, _, _ := cmdtesting.TestData()
   409  
   410  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   411  	defer tf.Cleanup()
   412  
   413  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   414  
   415  	tc := []struct {
   416  		name                      string
   417  		cmdArgs                   []string
   418  		forceFlag                 bool
   419  		nowFlag                   bool
   420  		gracePeriodFlag           string
   421  		expectedGracePeriod       string
   422  		expectedOut               string
   423  		expectedErrOut            string
   424  		expectedDeleteRequestPath string
   425  		expectedExitCode          int
   426  	}{
   427  		{
   428  			name:                      "Deleting an object with --force should use grace period = 0",
   429  			cmdArgs:                   []string{"pods/foo"},
   430  			forceFlag:                 true,
   431  			expectedGracePeriod:       "0",
   432  			expectedOut:               "pod/foo\n",
   433  			expectedErrOut:            "Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n",
   434  			expectedDeleteRequestPath: "/namespaces/test/pods/foo",
   435  		},
   436  		{
   437  			name:                      "Deleting an object with --force and --grace-period 0 should use grade period = 0",
   438  			cmdArgs:                   []string{"pods/foo"},
   439  			forceFlag:                 true,
   440  			gracePeriodFlag:           "0",
   441  			expectedGracePeriod:       "0",
   442  			expectedOut:               "pod/foo\n",
   443  			expectedErrOut:            "Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n",
   444  			expectedDeleteRequestPath: "/namespaces/test/pods/foo",
   445  		},
   446  		{
   447  			name:             "Deleting an object with --force and --grace-period > 0 should fail",
   448  			cmdArgs:          []string{"pods/foo"},
   449  			forceFlag:        true,
   450  			gracePeriodFlag:  "10",
   451  			expectedErrOut:   "error: --force and --grace-period greater than 0 cannot be specified together",
   452  			expectedExitCode: 1,
   453  		},
   454  		{
   455  			name:                      "Deleting an object with --grace-period 0 should use a grace period of 1",
   456  			cmdArgs:                   []string{"pods/foo"},
   457  			gracePeriodFlag:           "0",
   458  			expectedGracePeriod:       "1",
   459  			expectedOut:               "pod/foo\n",
   460  			expectedDeleteRequestPath: "/namespaces/test/pods/foo",
   461  		},
   462  		{
   463  			name:                      "Deleting an object with --grace-period > 0 should use the specified grace period",
   464  			cmdArgs:                   []string{"pods/foo"},
   465  			gracePeriodFlag:           "10",
   466  			expectedGracePeriod:       "10",
   467  			expectedOut:               "pod/foo\n",
   468  			expectedDeleteRequestPath: "/namespaces/test/pods/foo",
   469  		},
   470  		{
   471  			name:                      "Deleting an object with the --now flag should use grace period = 1",
   472  			cmdArgs:                   []string{"pods/foo"},
   473  			nowFlag:                   true,
   474  			expectedGracePeriod:       "1",
   475  			expectedOut:               "pod/foo\n",
   476  			expectedDeleteRequestPath: "/namespaces/test/pods/foo",
   477  		},
   478  		{
   479  			name:             "Deleting an object with --now and --grace-period should fail",
   480  			cmdArgs:          []string{"pods/foo"},
   481  			nowFlag:          true,
   482  			gracePeriodFlag:  "10",
   483  			expectedErrOut:   "error: --now and --grace-period cannot be specified together",
   484  			expectedExitCode: 1,
   485  		},
   486  	}
   487  
   488  	for _, test := range tc {
   489  		t.Run(test.name, func(t *testing.T) {
   490  
   491  			// Use a custom fatal behavior with panic/recover so that we can test failure scenarios where
   492  			// os.Exit() would normally be called
   493  			cmdutil.BehaviorOnFatal(func(actualErrOut string, actualExitCode int) {
   494  				if test.expectedExitCode != actualExitCode {
   495  					t.Errorf("unexpected exit code:\n\tExpected: %d\n\tActual:   %d\n", test.expectedExitCode, actualExitCode)
   496  				}
   497  				if test.expectedErrOut != actualErrOut {
   498  					t.Errorf("unexpected error:\n\tExpected: %s\n\tActual:   %s\n", test.expectedErrOut, actualErrOut)
   499  				}
   500  				panic(nil)
   501  			})
   502  			defer func() {
   503  				if test.expectedExitCode != 0 {
   504  					recover()
   505  				}
   506  			}()
   507  
   508  			// Setup a fake HTTP Client to capture whether a delete request was made or not and if so,
   509  			// the actual grace period that was used.
   510  			actualGracePeriod := ""
   511  			deleteOccurred := false
   512  			tf.UnstructuredClient = &fake.RESTClient{
   513  				NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   514  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   515  					switch p, m := req.URL.Path, req.Method; {
   516  					case m == "DELETE" && p == test.expectedDeleteRequestPath:
   517  						data := make(map[string]interface{})
   518  						_ = json.NewDecoder(req.Body).Decode(&data)
   519  						actualGracePeriod = strconv.FormatFloat(data["gracePeriodSeconds"].(float64), 'f', 0, 64)
   520  						deleteOccurred = true
   521  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
   522  					default:
   523  						t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   524  						return nil, nil
   525  					}
   526  				}),
   527  			}
   528  
   529  			// Test the command using the flags specified in the test case
   530  			streams, _, out, errOut := genericiooptions.NewTestIOStreams()
   531  			cmd := NewCmdDelete(tf, streams)
   532  			cmd.Flags().Set("output", "name")
   533  			if test.forceFlag {
   534  				cmd.Flags().Set("force", "true")
   535  			}
   536  			if test.nowFlag {
   537  				cmd.Flags().Set("now", "true")
   538  			}
   539  			if len(test.gracePeriodFlag) > 0 {
   540  				cmd.Flags().Set("grace-period", test.gracePeriodFlag)
   541  			}
   542  			cmd.Run(cmd, test.cmdArgs)
   543  
   544  			// Check actual vs expected conditions
   545  			if len(test.expectedDeleteRequestPath) > 0 && !deleteOccurred {
   546  				t.Errorf("expected http delete request to %s but it did not occur", test.expectedDeleteRequestPath)
   547  			}
   548  			if test.expectedGracePeriod != actualGracePeriod {
   549  				t.Errorf("unexpected grace period:\n\tExpected: %s\n\tActual:   %s\n", test.expectedGracePeriod, actualGracePeriod)
   550  			}
   551  			if out.String() != test.expectedOut {
   552  				t.Errorf("unexpected output:\n\tExpected: %s\n\tActual:   %s\n", test.expectedOut, out.String())
   553  			}
   554  			if errOut.String() != test.expectedErrOut {
   555  				t.Errorf("unexpected error output:\n\tExpected: %s\n\tActual:   %s\n", test.expectedErrOut, errOut.String())
   556  			}
   557  		})
   558  	}
   559  }
   560  
   561  func TestDeleteObjectNotFound(t *testing.T) {
   562  	cmdtesting.InitTestErrorHandler(t)
   563  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   564  	defer tf.Cleanup()
   565  
   566  	tf.UnstructuredClient = &fake.RESTClient{
   567  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   568  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   569  			switch p, m := req.URL.Path, req.Method; {
   570  			case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
   571  				return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
   572  			default:
   573  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   574  				return nil, nil
   575  			}
   576  		}),
   577  	}
   578  
   579  	options := &DeleteOptions{
   580  		FilenameOptions: resource.FilenameOptions{
   581  			Filenames: []string{"../../../testdata/redis-master-controller.yaml"},
   582  		},
   583  		GracePeriod:       -1,
   584  		CascadingStrategy: metav1.DeletePropagationOrphan,
   585  		Output:            "name",
   586  		IOStreams:         genericiooptions.NewTestIOStreamsDiscard(),
   587  	}
   588  	err := options.Complete(tf, []string{}, fakecmd())
   589  	if err != nil {
   590  		t.Errorf("unexpected error: %v", err)
   591  	}
   592  	err = options.RunDelete(nil)
   593  	if err == nil || !errors.IsNotFound(err) {
   594  		t.Errorf("unexpected error: expected NotFound, got %v", err)
   595  	}
   596  }
   597  
   598  func TestDeleteObjectIgnoreNotFound(t *testing.T) {
   599  	cmdtesting.InitTestErrorHandler(t)
   600  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   601  	defer tf.Cleanup()
   602  
   603  	tf.UnstructuredClient = &fake.RESTClient{
   604  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   605  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   606  			switch p, m := req.URL.Path, req.Method; {
   607  			case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
   608  				return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
   609  			default:
   610  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   611  				return nil, nil
   612  			}
   613  		}),
   614  	}
   615  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   616  
   617  	cmd := NewCmdDelete(tf, streams)
   618  	cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
   619  	cmd.Flags().Set("cascade", "false")
   620  	cmd.Flags().Set("ignore-not-found", "true")
   621  	cmd.Flags().Set("output", "name")
   622  	cmd.Run(cmd, []string{})
   623  
   624  	if buf.String() != "" {
   625  		t.Errorf("unexpected output: %s", buf.String())
   626  	}
   627  }
   628  
   629  func TestDeleteAllNotFound(t *testing.T) {
   630  	cmdtesting.InitTestErrorHandler(t)
   631  	_, svc, _ := cmdtesting.TestData()
   632  	// Add an item to the list which will result in a 404 on delete
   633  	svc.Items = append(svc.Items, corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
   634  	notFoundError := &errors.NewNotFound(corev1.Resource("services"), "foo").ErrStatus
   635  
   636  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   637  	defer tf.Cleanup()
   638  
   639  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   640  
   641  	tf.UnstructuredClient = &fake.RESTClient{
   642  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   643  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   644  			switch p, m := req.URL.Path, req.Method; {
   645  			case p == "/namespaces/test/services" && m == "GET":
   646  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svc)}, nil
   647  			case p == "/namespaces/test/services/foo" && m == "DELETE":
   648  				return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, notFoundError)}, nil
   649  			case p == "/namespaces/test/services/baz" && m == "DELETE":
   650  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
   651  			default:
   652  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   653  				return nil, nil
   654  			}
   655  		}),
   656  	}
   657  
   658  	// Make sure we can explicitly choose to fail on NotFound errors, even with --all
   659  	options := &DeleteOptions{
   660  		FilenameOptions:   resource.FilenameOptions{},
   661  		GracePeriod:       -1,
   662  		CascadingStrategy: metav1.DeletePropagationOrphan,
   663  		DeleteAll:         true,
   664  		IgnoreNotFound:    false,
   665  		Output:            "name",
   666  		IOStreams:         genericiooptions.NewTestIOStreamsDiscard(),
   667  	}
   668  	err := options.Complete(tf, []string{"services"}, fakecmd())
   669  	if err != nil {
   670  		t.Errorf("unexpected error: %v", err)
   671  	}
   672  	err = options.RunDelete(nil)
   673  	if err == nil || !errors.IsNotFound(err) {
   674  		t.Errorf("unexpected error: expected NotFound, got %v", err)
   675  	}
   676  }
   677  
   678  func TestDeleteAllIgnoreNotFound(t *testing.T) {
   679  	cmdtesting.InitTestErrorHandler(t)
   680  	_, svc, _ := cmdtesting.TestData()
   681  
   682  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   683  	defer tf.Cleanup()
   684  
   685  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   686  
   687  	// Add an item to the list which will result in a 404 on delete
   688  	svc.Items = append(svc.Items, corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
   689  	notFoundError := &errors.NewNotFound(corev1.Resource("services"), "foo").ErrStatus
   690  
   691  	tf.UnstructuredClient = &fake.RESTClient{
   692  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   693  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   694  			switch p, m := req.URL.Path, req.Method; {
   695  			case p == "/namespaces/test/services" && m == "GET":
   696  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svc)}, nil
   697  			case p == "/namespaces/test/services/foo" && m == "DELETE":
   698  				return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, notFoundError)}, nil
   699  			case p == "/namespaces/test/services/baz" && m == "DELETE":
   700  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
   701  			default:
   702  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   703  				return nil, nil
   704  			}
   705  		}),
   706  	}
   707  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   708  
   709  	cmd := NewCmdDelete(tf, streams)
   710  	cmd.Flags().Set("all", "true")
   711  	cmd.Flags().Set("cascade", "false")
   712  	cmd.Flags().Set("output", "name")
   713  	cmd.Run(cmd, []string{"services"})
   714  
   715  	if buf.String() != "service/baz\n" {
   716  		t.Errorf("unexpected output: %s", buf.String())
   717  	}
   718  }
   719  
   720  func TestDeleteMultipleObject(t *testing.T) {
   721  	cmdtesting.InitTestErrorHandler(t)
   722  	_, svc, rc := cmdtesting.TestData()
   723  
   724  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   725  	defer tf.Cleanup()
   726  
   727  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   728  
   729  	tf.UnstructuredClient = &fake.RESTClient{
   730  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   731  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   732  			switch p, m := req.URL.Path, req.Method; {
   733  			case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
   734  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   735  			case p == "/namespaces/test/services/frontend" && m == "DELETE":
   736  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
   737  			default:
   738  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   739  				return nil, nil
   740  			}
   741  		}),
   742  	}
   743  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   744  
   745  	cmd := NewCmdDelete(tf, streams)
   746  	cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
   747  	cmd.Flags().Set("filename", "../../../testdata/frontend-service.yaml")
   748  	cmd.Flags().Set("cascade", "false")
   749  	cmd.Flags().Set("output", "name")
   750  	cmd.Run(cmd, []string{})
   751  
   752  	if buf.String() != "replicationcontroller/redis-master\nservice/frontend\n" {
   753  		t.Errorf("unexpected output: %s", buf.String())
   754  	}
   755  }
   756  
   757  func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
   758  	cmdtesting.InitTestErrorHandler(t)
   759  	_, svc, _ := cmdtesting.TestData()
   760  
   761  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   762  	defer tf.Cleanup()
   763  
   764  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   765  
   766  	tf.UnstructuredClient = &fake.RESTClient{
   767  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   768  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   769  			switch p, m := req.URL.Path, req.Method; {
   770  			case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
   771  				return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
   772  			case p == "/namespaces/test/services/frontend" && m == "DELETE":
   773  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
   774  			default:
   775  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   776  				return nil, nil
   777  			}
   778  		}),
   779  	}
   780  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   781  
   782  	options := &DeleteOptions{
   783  		FilenameOptions: resource.FilenameOptions{
   784  			Filenames: []string{"../../../testdata/redis-master-controller.yaml", "../../../testdata/frontend-service.yaml"},
   785  		},
   786  		GracePeriod:       -1,
   787  		CascadingStrategy: metav1.DeletePropagationOrphan,
   788  		Output:            "name",
   789  		IOStreams:         streams,
   790  	}
   791  	err := options.Complete(tf, []string{}, fakecmd())
   792  	if err != nil {
   793  		t.Errorf("unexpected error: %v", err)
   794  	}
   795  	err = options.RunDelete(nil)
   796  	if err == nil || !errors.IsNotFound(err) {
   797  		t.Errorf("unexpected error: expected NotFound, got %v", err)
   798  	}
   799  
   800  	if buf.String() != "service/frontend\n" {
   801  		t.Errorf("unexpected output: %s", buf.String())
   802  	}
   803  }
   804  
   805  func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
   806  	cmdtesting.InitTestErrorHandler(t)
   807  	_, svc, rc := cmdtesting.TestData()
   808  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   809  	defer tf.Cleanup()
   810  
   811  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   812  
   813  	tf.UnstructuredClient = &fake.RESTClient{
   814  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   815  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   816  			switch p, m := req.URL.Path, req.Method; {
   817  			case p == "/namespaces/test/replicationcontrollers/baz" && m == "DELETE":
   818  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   819  			case p == "/namespaces/test/replicationcontrollers/foo" && m == "DELETE":
   820  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   821  			case p == "/namespaces/test/services/baz" && m == "DELETE":
   822  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
   823  			case p == "/namespaces/test/services/foo" && m == "DELETE":
   824  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
   825  			default:
   826  				// Ensures no GET is performed when deleting by name
   827  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   828  				return nil, nil
   829  			}
   830  		}),
   831  	}
   832  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   833  
   834  	cmd := NewCmdDelete(tf, streams)
   835  	cmd.Flags().Set("namespace", "test")
   836  	cmd.Flags().Set("cascade", "false")
   837  	cmd.Flags().Set("output", "name")
   838  	cmd.Run(cmd, []string{"replicationcontrollers,services", "baz", "foo"})
   839  	if buf.String() != "replicationcontroller/baz\nreplicationcontroller/foo\nservice/baz\nservice/foo\n" {
   840  		t.Errorf("unexpected output: %s", buf.String())
   841  	}
   842  }
   843  
   844  func TestDeleteDirectory(t *testing.T) {
   845  	cmdtesting.InitTestErrorHandler(t)
   846  	_, _, rc := cmdtesting.TestData()
   847  
   848  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   849  	defer tf.Cleanup()
   850  
   851  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   852  
   853  	tf.UnstructuredClient = &fake.RESTClient{
   854  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   855  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   856  			switch p, m := req.URL.Path, req.Method; {
   857  			case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == "DELETE":
   858  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
   859  			default:
   860  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   861  				return nil, nil
   862  			}
   863  		}),
   864  	}
   865  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   866  
   867  	cmd := NewCmdDelete(tf, streams)
   868  	cmd.Flags().Set("filename", "../../../testdata/replace/legacy")
   869  	cmd.Flags().Set("cascade", "false")
   870  	cmd.Flags().Set("output", "name")
   871  	cmd.Run(cmd, []string{})
   872  
   873  	if buf.String() != "replicationcontroller/frontend\nreplicationcontroller/redis-master\nreplicationcontroller/redis-slave\n" {
   874  		t.Errorf("unexpected output: %s", buf.String())
   875  	}
   876  }
   877  
   878  func TestDeleteMultipleSelector(t *testing.T) {
   879  	cmdtesting.InitTestErrorHandler(t)
   880  	pods, svc, _ := cmdtesting.TestData()
   881  
   882  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   883  	defer tf.Cleanup()
   884  
   885  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   886  
   887  	tf.UnstructuredClient = &fake.RESTClient{
   888  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   889  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   890  			switch p, m := req.URL.Path, req.Method; {
   891  			case p == "/namespaces/test/pods" && m == "GET":
   892  				if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
   893  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   894  				}
   895  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, nil
   896  			case p == "/namespaces/test/services" && m == "GET":
   897  				if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
   898  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   899  				}
   900  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svc)}, nil
   901  			case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
   902  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
   903  			case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
   904  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
   905  			default:
   906  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   907  				return nil, nil
   908  			}
   909  		}),
   910  	}
   911  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   912  
   913  	cmd := NewCmdDelete(tf, streams)
   914  	cmd.Flags().Set("selector", "a=b")
   915  	cmd.Flags().Set("cascade", "false")
   916  	cmd.Flags().Set("output", "name")
   917  	cmd.Run(cmd, []string{"pods,services"})
   918  
   919  	if buf.String() != "pod/foo\npod/bar\nservice/baz\n" {
   920  		t.Errorf("unexpected output: %s", buf.String())
   921  	}
   922  }
   923  
   924  func TestResourceErrors(t *testing.T) {
   925  	cmdtesting.InitTestErrorHandler(t)
   926  	testCases := map[string]struct {
   927  		args  []string
   928  		errFn func(error) bool
   929  	}{
   930  		"no args": {
   931  			args:  []string{},
   932  			errFn: func(err error) bool { return strings.Contains(err.Error(), "You must provide one or more resources") },
   933  		},
   934  		"resources but no selectors": {
   935  			args: []string{"pods"},
   936  			errFn: func(err error) bool {
   937  				return strings.Contains(err.Error(), "resource(s) were provided, but no name was specified")
   938  			},
   939  		},
   940  		"multiple resources but no selectors": {
   941  			args: []string{"pods,deployments"},
   942  			errFn: func(err error) bool {
   943  				return strings.Contains(err.Error(), "resource(s) were provided, but no name was specified")
   944  			},
   945  		},
   946  	}
   947  
   948  	for k, testCase := range testCases {
   949  		t.Run(k, func(t *testing.T) {
   950  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   951  			defer tf.Cleanup()
   952  
   953  			tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
   954  
   955  			streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   956  			options := &DeleteOptions{
   957  				FilenameOptions:   resource.FilenameOptions{},
   958  				GracePeriod:       -1,
   959  				CascadingStrategy: metav1.DeletePropagationOrphan,
   960  				Output:            "name",
   961  				IOStreams:         streams,
   962  			}
   963  			err := options.Complete(tf, testCase.args, fakecmd())
   964  			if !testCase.errFn(err) {
   965  				t.Errorf("%s: unexpected error: %v", k, err)
   966  				return
   967  			}
   968  
   969  			if buf.Len() > 0 {
   970  				t.Errorf("buffer should be empty: %s", buf.String())
   971  			}
   972  		})
   973  	}
   974  }
   975  

View as plain text