github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/commands/alpha/repo/reg/command_test.go (about)

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package reg
    16  
    17  import (
    18  	"encoding/json"
    19  	"flag"
    20  	"io"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"testing"
    27  
    28  	fakeprinter "github.com/GoogleContainerTools/kpt/internal/printer/fake"
    29  	"github.com/google/go-cmp/cmp"
    30  	"gopkg.in/yaml.v3"
    31  	"k8s.io/cli-runtime/pkg/genericclioptions"
    32  	"k8s.io/client-go/rest"
    33  )
    34  
    35  var (
    36  	update = flag.Bool("update", false, "update golden files")
    37  )
    38  
    39  func TestMain(m *testing.M) {
    40  	flag.Parse()
    41  	os.Exit(m.Run())
    42  }
    43  
    44  type httpAction struct {
    45  	method       string
    46  	path         string
    47  	wantRequest  string
    48  	sendResponse string
    49  }
    50  
    51  type testcase struct {
    52  	name    string
    53  	args    []string
    54  	actions []httpAction // http request to expect and responses to send back
    55  }
    56  
    57  func TestRepoReg(t *testing.T) {
    58  	testdata, err := filepath.Abs(filepath.Join(".", "testdata"))
    59  	if err != nil {
    60  		t.Fatalf("Failed to find testdata: %v", err)
    61  	}
    62  
    63  	for _, tc := range []testcase{
    64  		{
    65  			name: "SimpleRegister",
    66  			args: []string{"https://github.com/platkrm/test-blueprints"},
    67  			actions: []httpAction{
    68  				{
    69  					method:       http.MethodPost,
    70  					path:         "/apis/config.porch.kpt.dev/v1alpha1/namespaces/default/repositories",
    71  					wantRequest:  "simple-repository.yaml",
    72  					sendResponse: "simple-repository.yaml",
    73  				},
    74  			},
    75  		},
    76  		{
    77  			name: "AuthRegister",
    78  			args: []string{"https://github.com/platkrm/test-blueprints.git", "--repo-basic-username=test-username", "--repo-basic-password=test-password"},
    79  			actions: []httpAction{
    80  				{
    81  					method:       http.MethodPost,
    82  					path:         "/api/v1/namespaces/default/secrets",
    83  					wantRequest:  "auth-secret.yaml",
    84  					sendResponse: "auth-secret.yaml",
    85  				},
    86  				{
    87  					method:       http.MethodPost,
    88  					path:         "/apis/config.porch.kpt.dev/v1alpha1/namespaces/default/repositories",
    89  					wantRequest:  "auth-repository.yaml",
    90  					sendResponse: "auth-repository.yaml",
    91  				},
    92  			},
    93  		},
    94  		{
    95  			name: "FullRegister",
    96  			args: []string{
    97  				"https://github.com/platkrm/test-blueprints.git",
    98  				"--name=repository-resource-name",
    99  				"--description=\"Test Repository Description\"",
   100  				"--deployment",
   101  				"--directory=/catalog",
   102  				"--branch=main-branch",
   103  				"--create-branch",
   104  				"--namespace=repository-namespace",
   105  			},
   106  			actions: []httpAction{
   107  				{
   108  					method:       http.MethodPost,
   109  					path:         "/apis/config.porch.kpt.dev/v1alpha1/namespaces/repository-namespace/repositories",
   110  					wantRequest:  "full-repository.yaml",
   111  					sendResponse: "full-repository.yaml",
   112  				},
   113  			},
   114  		},
   115  	} {
   116  		t.Run(tc.name, func(t *testing.T) {
   117  			// Create fake Porch Server
   118  			porch := createFakePorch(t, tc.actions, func(action httpAction, w http.ResponseWriter, r *http.Request) {
   119  				// TODO: contents of this function is generic; move to shared utility in testutil.
   120  				var requestBody []byte
   121  				switch r.Header.Get("Content-Encoding") {
   122  				case "":
   123  					b, err := io.ReadAll(r.Body)
   124  					if err != nil {
   125  						t.Fatalf("Failed to read request body: %v", err)
   126  					}
   127  					requestBody = b
   128  
   129  				default:
   130  					t.Fatalf("unhandled content-encoding %q", r.Header.Get("Content-Encoding"))
   131  				}
   132  
   133  				var body interface{}
   134  				switch r.Header.Get("Content-Type") {
   135  				case "application/json":
   136  					if err := json.Unmarshal(requestBody, &body); err != nil {
   137  						t.Fatalf("Failed to unmarshal body: %v\n%s\n", err, string(requestBody))
   138  					}
   139  
   140  				// case "application/vnd.kubernetes.protobuf":
   141  				// Proto encoding is not handled https://kubernetes.io/docs/reference/using-api/api-concepts/#protobuf-encoding
   142  
   143  				default:
   144  					t.Fatalf("unhandled content-type %q", r.Header.Get("Content-Type"))
   145  				}
   146  
   147  				wantFile := filepath.Join(testdata, action.wantRequest)
   148  
   149  				if *update {
   150  					data, err := yaml.Marshal(body)
   151  					if err != nil {
   152  						t.Fatalf("Failed to marshal request body as YAML: %v", err)
   153  					}
   154  					if err := os.WriteFile(wantFile, data, 0644); err != nil {
   155  						t.Fatalf("Failed to update golden file %q: %v", wantFile, err)
   156  					}
   157  				}
   158  
   159  				var want interface{}
   160  				wantBytes, err := os.ReadFile(wantFile)
   161  				if err != nil {
   162  					t.Fatalf("Failed to reead golden file %q: %v", wantFile, err)
   163  				}
   164  				if err := yaml.Unmarshal(wantBytes, &want); err != nil {
   165  					t.Fatalf("Failed to unmarshal expected body %q: %v", wantFile, err)
   166  				}
   167  
   168  				if !cmp.Equal(want, body) {
   169  					t.Errorf("Unexpected request body for %q (-want, +got) %s", r.RequestURI, cmp.Diff(want, body))
   170  				}
   171  
   172  				respData, err := os.ReadFile(filepath.Join(testdata, action.sendResponse))
   173  				if err != nil {
   174  					t.Fatalf("Failed to read response file %q: %v", action.sendResponse, err)
   175  				}
   176  				var resp interface{}
   177  				if err := yaml.Unmarshal(respData, &resp); err != nil {
   178  					t.Fatalf("Failed to unmarshal desired response %q: %v", action.sendResponse, err)
   179  				}
   180  				respJSON, err := json.Marshal(resp)
   181  				if err != nil {
   182  					t.Fatalf("Failed to marshal response body as JSON: %v", err)
   183  				}
   184  
   185  				w.Header().Add("Content-Type", "application/json")
   186  				w.WriteHeader(http.StatusOK)
   187  				if _, err := w.Write(respJSON); err != nil {
   188  					t.Errorf("Failed to write resonse body %q: %v", action.sendResponse, err)
   189  				}
   190  			})
   191  
   192  			// Create a test HTTP server.
   193  			server := httptest.NewServer(porch)
   194  			defer server.Close()
   195  
   196  			// Create Kubeconfig
   197  			url := server.URL
   198  			usePersistentConfig := false
   199  			rcg := genericclioptions.NewConfigFlags(usePersistentConfig)
   200  			rcg.APIServer = &url
   201  			rcg.WrapConfigFn = func(restConfig *rest.Config) *rest.Config {
   202  				// Force use of JSON encoding
   203  				restConfig.ContentType = "application/json"
   204  				return restConfig
   205  			}
   206  			namespace := "default"
   207  			rcg.Namespace = &namespace
   208  			ctx := fakeprinter.CtxWithDefaultPrinter()
   209  
   210  			cmd := NewCommand(ctx, rcg)
   211  			rcg.AddFlags(cmd.PersistentFlags()) // Add global flags
   212  			cmd.SetArgs(tc.args)
   213  			if err := cmd.Execute(); err != nil {
   214  				t.Errorf("Executing repo register %s failed: %v", strings.Join(tc.args, " "), err)
   215  			}
   216  		})
   217  	}
   218  }
   219  
   220  func createFakePorch(t *testing.T, actions []httpAction, handler func(action httpAction, w http.ResponseWriter, r *http.Request)) *fakePorch {
   221  	actionMap := map[request]httpAction{}
   222  	for _, a := range actions {
   223  		actionMap[request{
   224  			method: a.method,
   225  			url:    a.path,
   226  		}] = a
   227  	}
   228  	return &fakePorch{
   229  		T:       t,
   230  		actions: actionMap,
   231  		handler: handler,
   232  	}
   233  }
   234  
   235  // TODO: Move the below to shared testing utility
   236  type request struct {
   237  	method, url string
   238  }
   239  
   240  type fakePorch struct {
   241  	*testing.T
   242  	actions map[request]httpAction
   243  	handler func(action httpAction, w http.ResponseWriter, r *http.Request)
   244  }
   245  
   246  var _ http.Handler = &fakePorch{}
   247  
   248  func (p *fakePorch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   249  	p.Logf("%s\n", r.RequestURI)
   250  	action, ok := p.actions[request{method: r.Method, url: r.URL.Path}]
   251  	if !ok {
   252  		p.Logf("handler not found for method %q url %q", r.Method, r.URL.Path)
   253  		w.WriteHeader(http.StatusNotFound)
   254  		return
   255  	}
   256  	p.handler(action, w, r)
   257  }