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 }