sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/io/opener_test.go (about) 1 /* 2 Copyright 2020 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 io 18 19 import ( 20 "context" 21 "encoding/base64" 22 "errors" 23 "fmt" 24 "net/http" 25 "os" 26 "strings" 27 "testing" 28 29 "cloud.google.com/go/storage" 30 "google.golang.org/api/googleapi" 31 ) 32 33 func Test_opener_SignedURL(t *testing.T) { 34 // This fake key is revoked and thus worthless but still make its contents less obvious 35 fakeKeyBuf, err := base64.StdEncoding.DecodeString(` 36 LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tXG5NSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVG 37 QUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUUN4MEF2aW1yMjcwZDdaXG5pamw3b1FRUW1oZTFOb3dpeWMy 38 UStuQW95aFE1YkQvUW1jb01zcWg2YldneVI0UU90aXVBbHM2VWhJenF4Q25pXG5PazRmbWJqVnhp 39 STl1Ri9EVTV6ZE5wM0dkQWFiUlVPNW5yWkpMelN0VXhudFBEcjZvK281RHM5YWJJWkNYYUVTXG5o 40 UWxOdTBrUm5HbHZGUHNkV1JYMmtSN01Yb3pkcXczcHZZRXZyaGlhRStYZnRhUzhKdmZEc0NPT2RQ 41 OWp5TzNTXG5aR2lkaU5hRmhYK2xnZEcrdHdqOUE3UDFlb1NMbTZCdXVhcjRDOGhlOEVkVGVEbXVk 42 a1BPeWwvb2tHWU5tSzJkXG5yUkQ0WHBhcy93VGxsTXBLRUZxWllZeVdkRnJvVWQwMFVhQnhHV0cz 43 UlZ2TWZoRk80QUhrSkNwZlE1U00rSElmXG5VN2lkRjAyYkFnTUJBQUVDZ2dFQURIaVhoTTZ1bFFB 44 OHZZdzB5T2Q3cGdCd3ZqeHpxckwxc0gvb0l1dzlhK09jXG5QREMxRzV2aU5pZjdRVitEc3haeXlh 45 T0tISitKVktQcWZodnh3OFNmMHBxQlowdkpwNlR6SVE3R0ZSZXBLUFc4XG5NTVloYWRPZVFiUE00 46 emN3dWNpS1VuTW45dU1hcllmc2xxUnZDUjBrSEZDWWtucHB2RjYxckNQMGdZZjJJRXZUXG5qNVlV 47 QWFrNDlVRDQyaUdEZnh2OGUzMGlMTmRRWE1iMHE3V2dyRGdxL0ttUHM2Q2dOaGRzME1uSlRFbUE5 48 YlFtXG52MHV0K2hUYWpXalcxVWNyUTBnM2JjNng1VWN2V1VjK1ZndUllVmxVcEgvM2dJNXVYZkxn 49 bTVQNThNa0s4UlhTXG5YYW92Rk05VkNNRFhTK25PWk1uSXoyNVd5QmhkNmdpVWs5UkJhc05Tb1FL 50 QmdRRGFxUXpyYWJUZEZNY1hwVlNnXG41TUpuNEcvSFVPWUxveVM5cE9UZi9qbFN1ZUYrNkt6RGJV 51 N1F6TC9wT1JtYjJldVdxdmpmZDVBaU1oUnY2Snk1XG41ZVNpa3dYRDZJeS9sZGh3QUdtMUZrZ1ZX 52 TXJ3ZHlqYjJpV2I2Um4rNXRBYjgwdzNEN2ZTWWhEWkxUOWJCNjdCXG4ybGxiOGFycEJRcndZUFFB 53 U2pUVUVYQnVJUUtCZ1FEUUxVemkrd0tHNEhLeko1NE1sQjFoR3cwSFZlWEV4T0pmXG53bS9IVjhl 54 aThDeHZLMTRoRXpCT3JXQi9aNlo4VFFxWnA0eENnYkNiY0hwY3pLRUxvcDA2K2hqa1N3ZkR2TUJZ 55 XG5mNnN6U2RSenNYVTI1NndmcG1hRjJ0TlJZZFpVblh2QWc5MFIrb1BFSjhrRHd4cjdiMGZmL3lu 56 b0UrWUx0ckowXG53dklad3Joc093S0JnQWVPbWlTMHRZeUNnRkwvNHNuZ3ZodEs5WElGQ0w1VU9C 57 dlp6Qk0xdlJOdjJ5eEFyRi9nXG5zajJqSmVyUWoyTUVpQkRmL2RQelZPYnBwaTByOCthMDNFOEdG 58 OGZxakpxK2VnbDg2aXBaQjhxOUU5NTFyOUxSXG5Xa1ZtTEFEVVIxTC8rSjFhakxiWHJzOWlzZkxh 59 ZEI2OUJpT1lXWmpPRk0reitocmNkYkR5blZraEFvR0FJbW42XG50ZU1zN2NNWTh3anZsY0MrZ3Br 60 SU5GZzgzYVIyajhJQzNIOWtYMGs0N3ovS0ZjbW9TTGxjcEhNc0VJeGozamJXXG5kd0FkZy9TNkpi 61 RW1SbGdoaWVoaVNRc21RM05ta0xxNlFJWkorcjR4VkZ4RUZnOWFEM0szVUZMT0xickRCSFpJXG5D 62 M3JRWVpMNkpnY1E1TlBtbTk4QXZIN2RucjRiRGpaVDgzSS9McFVDZ1lFQWttNXlvVUtZY0tXMVQz 63 R1hadUNIXG40SDNWVGVzZDZyb3pKWUhmTWVkNE9jQ3l1bnBIVmZmSmFCMFIxRjZ2MjFQaitCVWlW 64 WjBzU010RjEvTE1uQkc4XG5TQVlQUnVxOHVNUUdNQTFpdE1Hc2VhMmg1V2RhbXNGODhXRFd4VEoy 65 QXVnblJHNERsdmJLUDhPQmVLUFFKeDhEXG5RMzJ2SVpNUVkyV1hVMVhwUkMrNWs5RT1cbi0tLS0t 66 RU5EIFBSSVZBVEUgS0VZLS0tLS1cbgo=`) 67 if err != nil { 68 t.Fatalf("Failed to decode fake key: %v", err) 69 } 70 fakePrivateKey := strings.TrimSpace(string(fakeKeyBuf)) 71 type args struct { 72 ctx context.Context 73 p string 74 opts SignedURLOptions 75 } 76 tests := []struct { 77 name string 78 args args 79 fakeCreds string 80 want string 81 contains []string 82 wantErr bool 83 }{ 84 { 85 name: "anon auth works", 86 args: args{ 87 p: "gs://foo/bar/stuff", 88 }, 89 want: fmt.Sprintf("https://%s/foo/bar/stuff", GSAnonHost), 90 }, 91 { 92 name: "cookie auth works", 93 args: args{ 94 p: "gs://foo/bar/stuff", 95 opts: SignedURLOptions{ 96 UseGSCookieAuth: true, 97 }, 98 }, 99 want: fmt.Sprintf("https://%s/foo/bar/stuff", GSCookieHost), 100 }, 101 { 102 name: "signed URLs work", 103 args: args{ 104 p: "gs://foo/bar/stuff", 105 }, 106 fakeCreds: `{ 107 "type": "service_account", 108 "private_key": "` + fakePrivateKey + `", 109 "client_email": "fake-user@k8s.io" 110 }`, 111 contains: []string{ 112 "https://storage.googleapis.com/foo/bar/stuff?", 113 "GoogleAccessId=fake-user%40k8s.io", 114 "Signature=", // Do not particularly care about the Signature contents 115 }, 116 }, 117 } 118 for _, tt := range tests { 119 t.Run(tt.name, func(t *testing.T) { 120 var gcsCredentialsFile string 121 if tt.fakeCreds != "" { 122 fp, err := os.CreateTemp("", "fake-creds") 123 if err != nil { 124 t.Fatalf("Failed to create fake creds: %v", err) 125 } 126 127 gcsCredentialsFile = fp.Name() 128 defer os.Remove(gcsCredentialsFile) 129 if _, err := fp.Write([]byte(tt.fakeCreds)); err != nil { 130 t.Fatalf("Failed to write fake creds %s: %v", gcsCredentialsFile, err) 131 } 132 133 if err := fp.Close(); err != nil { 134 t.Fatalf("Failed to close fake creds %s: %v", gcsCredentialsFile, err) 135 } 136 } 137 o, _ := NewOpener(context.Background(), gcsCredentialsFile, "") 138 got, err := o.SignedURL(tt.args.ctx, tt.args.p, tt.args.opts) 139 if (err != nil) != tt.wantErr { 140 t.Errorf("SignedURL() error = %v, wantErr %v", err, tt.wantErr) 141 return 142 } 143 if tt.want != "" && got != tt.want { 144 t.Errorf("SignedURL() got = %v, want %v", got, tt.want) 145 } 146 if len(tt.contains) > 0 { 147 for _, contains := range tt.contains { 148 if !strings.Contains(got, contains) { 149 t.Errorf("SignedURL() got = %q, does not contain %q", got, contains) 150 } 151 } 152 } 153 }) 154 } 155 } 156 157 func TestIsNotExist(t *testing.T) { 158 t.Parallel() 159 testCases := []struct { 160 name string 161 err error 162 expectMatch bool 163 }{ 164 { 165 name: "Direct ErrNotExist", 166 err: os.ErrNotExist, 167 expectMatch: true, 168 }, 169 { 170 name: "Direct storage.ErrObjectNotExist", 171 err: storage.ErrObjectNotExist, 172 expectMatch: true, 173 }, 174 { 175 name: "Direct other", 176 err: errors.New("not workx"), 177 expectMatch: false, 178 }, 179 { 180 name: "Wrapped ErrNotExist", 181 err: fmt.Errorf("around: %w", os.ErrNotExist), 182 expectMatch: true, 183 }, 184 { 185 name: "Wrapped storage.ErrObjectNotExist", 186 err: fmt.Errorf("around: %w", storage.ErrObjectNotExist), 187 expectMatch: true, 188 }, 189 { 190 name: "Wrapped other", 191 err: fmt.Errorf("I see it %w", errors.New("not workx")), 192 expectMatch: false, 193 }, 194 { 195 name: "Don't panic", 196 expectMatch: false, 197 }, 198 } 199 200 for _, tc := range testCases { 201 t.Run(tc.name, func(t *testing.T) { 202 if result := IsNotExist(tc.err); result != tc.expectMatch { 203 t.Errorf("expect match: %t, got match: %t", tc.expectMatch, result) 204 } 205 }) 206 } 207 } 208 209 func TestIsErrUnexpected(t *testing.T) { 210 tests := []struct { 211 name string 212 err error 213 unexpected bool 214 }{ 215 { 216 name: "standard errors are unexpected", 217 err: errors.New("this is just a normal error"), 218 unexpected: true, 219 }, 220 { 221 name: "nil errors are expected", 222 err: nil, 223 unexpected: false, 224 }, 225 { 226 name: "googleapi errors other than Precondition Failed are unexpected", 227 err: &googleapi.Error{Code: http.StatusNotFound}, 228 unexpected: true, 229 }, 230 { 231 name: "Precondition Failed googleapi errors are expected", 232 err: &googleapi.Error{Code: http.StatusPreconditionFailed}, 233 unexpected: false, 234 }, 235 } 236 237 for _, tc := range tests { 238 t.Run(tc.name, func(t *testing.T) { 239 result := isErrUnexpected(tc.err) 240 if result != tc.unexpected { 241 t.Errorf("Expected isErrUnexpected() to return %v, got %v", tc.unexpected, result) 242 } 243 }) 244 } 245 }