github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/cf/api/applicationbits/application_bits_test.go (about) 1 package applicationbits_test 2 3 import ( 4 "archive/zip" 5 "fmt" 6 "log" 7 "net/http" 8 "net/http/httptest" 9 "os" 10 "path/filepath" 11 "runtime" 12 "strconv" 13 "strings" 14 "time" 15 16 testapi "code.cloudfoundry.org/cli/cf/api/apifakes" 17 "code.cloudfoundry.org/cli/cf/api/resources" 18 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 19 "code.cloudfoundry.org/cli/cf/net" 20 "code.cloudfoundry.org/cli/cf/terminal/terminalfakes" 21 testconfig "code.cloudfoundry.org/cli/util/testhelpers/configuration" 22 testnet "code.cloudfoundry.org/cli/util/testhelpers/net" 23 24 . "code.cloudfoundry.org/cli/cf/api/applicationbits" 25 "code.cloudfoundry.org/cli/cf/trace/tracefakes" 26 . "github.com/onsi/ginkgo" 27 . "github.com/onsi/gomega" 28 ) 29 30 var _ = Describe("CloudControllerApplicationBitsRepository", func() { 31 var ( 32 fixturesDir string 33 repo Repository 34 file1 resources.AppFileResource 35 file2 resources.AppFileResource 36 file3 resources.AppFileResource 37 file4 resources.AppFileResource 38 testServer *httptest.Server 39 configRepo coreconfig.ReadWriter 40 ) 41 42 BeforeEach(func() { 43 cwd, err := os.Getwd() 44 Expect(err).NotTo(HaveOccurred()) 45 fixturesDir = filepath.Join(cwd, "../../../fixtures/applications") 46 47 configRepo = testconfig.NewRepositoryWithDefaults() 48 49 gateway := net.NewCloudControllerGateway(configRepo, time.Now, new(terminalfakes.FakeUI), new(tracefakes.FakePrinter), "") 50 gateway.PollingThrottle = time.Duration(0) 51 52 repo = NewCloudControllerApplicationBitsRepository(configRepo, gateway) 53 54 file1 = resources.AppFileResource{Path: "app.rb", Sha1: "2474735f5163ba7612ef641f438f4b5bee00127b", Size: 51} 55 file2 = resources.AppFileResource{Path: "config.ru", Sha1: "f097424ce1fa66c6cb9f5e8a18c317376ec12e05", Size: 70} 56 file3 = resources.AppFileResource{Path: "Gemfile", Sha1: "d9c3a51de5c89c11331d3b90b972789f1a14699a", Size: 59, Mode: "0750"} 57 file4 = resources.AppFileResource{Path: "Gemfile.lock", Sha1: "345f999aef9070fb9a608e65cf221b7038156b6d", Size: 229, Mode: "0600"} 58 }) 59 60 setupTestServer := func(reqs ...testnet.TestRequest) { 61 testServer, _ = testnet.NewServer(reqs) 62 configRepo.SetAPIEndpoint(testServer.URL) 63 } 64 65 Describe(".UploadBits", func() { 66 var uploadFile *os.File 67 var err error 68 69 BeforeEach(func() { 70 uploadFile, err = os.Open(filepath.Join(fixturesDir, "ignored_and_resource_matched_example_app.zip")) 71 if err != nil { 72 log.Fatal(err) 73 } 74 }) 75 76 AfterEach(func() { 77 testServer.Close() 78 }) 79 80 It("uploads zip files", func() { 81 setupTestServer(testapi.NewCloudControllerTestRequest(testnet.TestRequest{ 82 Method: "PUT", 83 Path: "/v2/apps/my-cool-app-guid/bits", 84 Matcher: uploadBodyMatcher(defaultZipCheck), 85 Response: testnet.TestResponse{ 86 Status: http.StatusCreated, 87 Body: ` 88 { 89 "metadata":{ 90 "guid": "my-job-guid", 91 "url": "/v2/jobs/my-job-guid" 92 } 93 }`, 94 }, 95 }), 96 createProgressEndpoint("running"), 97 createProgressEndpoint("finished"), 98 ) 99 100 apiErr := repo.UploadBits("my-cool-app-guid", uploadFile, []resources.AppFileResource{file1, file2}) 101 Expect(apiErr).NotTo(HaveOccurred()) 102 }) 103 104 It("returns a failure when uploading bits fails", func() { 105 setupTestServer(testapi.NewCloudControllerTestRequest(testnet.TestRequest{ 106 Method: "PUT", 107 Path: "/v2/apps/my-cool-app-guid/bits", 108 Matcher: uploadBodyMatcher(defaultZipCheck), 109 Response: testnet.TestResponse{ 110 Status: http.StatusCreated, 111 Body: ` 112 { 113 "metadata":{ 114 "guid": "my-job-guid", 115 "url": "/v2/jobs/my-job-guid" 116 } 117 }`, 118 }, 119 }), 120 createProgressEndpoint("running"), 121 createProgressEndpoint("failed"), 122 ) 123 apiErr := repo.UploadBits("my-cool-app-guid", uploadFile, []resources.AppFileResource{file1, file2}) 124 125 Expect(apiErr).To(HaveOccurred()) 126 }) 127 128 Context("when there are no files to upload", func() { 129 It("makes a request without a zipfile", func() { 130 setupTestServer( 131 testapi.NewCloudControllerTestRequest(testnet.TestRequest{ 132 Method: "PUT", 133 Path: "/v2/apps/my-cool-app-guid/bits", 134 Matcher: func(request *http.Request) { 135 err := request.ParseMultipartForm(maxMultipartResponseSizeInBytes) 136 Expect(err).NotTo(HaveOccurred()) 137 defer request.MultipartForm.RemoveAll() 138 139 Expect(len(request.MultipartForm.Value)).To(Equal(1), "Should have 1 value") 140 valuePart, ok := request.MultipartForm.Value["resources"] 141 142 Expect(ok).To(BeTrue(), "Resource manifest not present") 143 Expect(valuePart).To(Equal([]string{"[]"})) 144 Expect(request.MultipartForm.File).To(BeEmpty()) 145 }, 146 Response: testnet.TestResponse{ 147 Status: http.StatusCreated, 148 Body: ` 149 { 150 "metadata":{ 151 "guid": "my-job-guid", 152 "url": "/v2/jobs/my-job-guid" 153 } 154 }`, 155 }, 156 }), 157 createProgressEndpoint("running"), 158 createProgressEndpoint("finished"), 159 ) 160 161 apiErr := repo.UploadBits("my-cool-app-guid", nil, []resources.AppFileResource{}) 162 Expect(apiErr).NotTo(HaveOccurred()) 163 }) 164 }) 165 166 It("marshals a nil presentFiles parameter into an empty array", func() { 167 setupTestServer( 168 testapi.NewCloudControllerTestRequest(testnet.TestRequest{ 169 Method: "PUT", 170 Path: "/v2/apps/my-cool-app-guid/bits", 171 Matcher: func(request *http.Request) { 172 err := request.ParseMultipartForm(maxMultipartResponseSizeInBytes) 173 Expect(err).NotTo(HaveOccurred()) 174 defer request.MultipartForm.RemoveAll() 175 176 Expect(len(request.MultipartForm.Value)).To(Equal(1), "Should have 1 value") 177 valuePart, ok := request.MultipartForm.Value["resources"] 178 179 Expect(ok).To(BeTrue(), "Resource manifest not present") 180 Expect(valuePart).To(Equal([]string{"[]"})) 181 Expect(request.MultipartForm.File).To(BeEmpty()) 182 }, 183 Response: testnet.TestResponse{ 184 Status: http.StatusCreated, 185 Body: ` 186 { 187 "metadata":{ 188 "guid": "my-job-guid", 189 "url": "/v2/jobs/my-job-guid" 190 } 191 }`, 192 }, 193 }), 194 createProgressEndpoint("running"), 195 createProgressEndpoint("finished"), 196 ) 197 198 apiErr := repo.UploadBits("my-cool-app-guid", nil, nil) 199 Expect(apiErr).NotTo(HaveOccurred()) 200 }) 201 }) 202 203 Describe(".GetApplicationFiles", func() { 204 It("accepts a slice of files and returns a slice of the files that it already has", func() { 205 setupTestServer(matchResourceRequest) 206 matchedFiles, err := repo.GetApplicationFiles([]resources.AppFileResource{file1, file2, file3, file4}) 207 Expect(matchedFiles).To(Equal([]resources.AppFileResource{file3, file4})) 208 Expect(err).NotTo(HaveOccurred()) 209 }) 210 211 It("excludes files that were in the response but not in the request", func() { 212 setupTestServer(matchResourceRequestImbalanced) 213 matchedFiles, err := repo.GetApplicationFiles([]resources.AppFileResource{file1, file4}) 214 Expect(matchedFiles).To(Equal([]resources.AppFileResource{file4})) 215 Expect(err).NotTo(HaveOccurred()) 216 }) 217 }) 218 }) 219 220 var matchedResources = testnet.RemoveWhiteSpaceFromBody(`[ 221 { 222 "sha1": "d9c3a51de5c89c11331d3b90b972789f1a14699a", 223 "size": 59 224 }, 225 { 226 "sha1": "345f999aef9070fb9a608e65cf221b7038156b6d", 227 "size": 229 228 } 229 ]`) 230 231 var unmatchedResources = testnet.RemoveWhiteSpaceFromBody(`[ 232 { 233 "sha1": "2474735f5163ba7612ef641f438f4b5bee00127b", 234 "size": 51, 235 "fn": "app.rb", 236 "mode":"" 237 }, 238 { 239 "sha1": "f097424ce1fa66c6cb9f5e8a18c317376ec12e05", 240 "size": 70, 241 "fn": "config.ru", 242 "mode":"" 243 } 244 ]`) 245 246 func uploadApplicationRequest(zipCheck func(*zip.Reader)) testnet.TestRequest { 247 return testapi.NewCloudControllerTestRequest(testnet.TestRequest{ 248 Method: "PUT", 249 Path: "/v2/apps/my-cool-app-guid/bits", 250 Matcher: uploadBodyMatcher(zipCheck), 251 Response: testnet.TestResponse{ 252 Status: http.StatusCreated, 253 Body: ` 254 { 255 "metadata":{ 256 "guid": "my-job-guid", 257 "url": "/v2/jobs/my-job-guid" 258 } 259 } 260 `}, 261 }) 262 } 263 264 var matchResourceRequest = testnet.TestRequest{ 265 Method: "PUT", 266 Path: "/v2/resource_match", 267 Matcher: testnet.RequestBodyMatcher(testnet.RemoveWhiteSpaceFromBody(`[ 268 { 269 "sha1": "2474735f5163ba7612ef641f438f4b5bee00127b", 270 "size": 51 271 }, 272 { 273 "sha1": "f097424ce1fa66c6cb9f5e8a18c317376ec12e05", 274 "size": 70 275 }, 276 { 277 "sha1": "d9c3a51de5c89c11331d3b90b972789f1a14699a", 278 "size": 59 279 }, 280 { 281 "sha1": "345f999aef9070fb9a608e65cf221b7038156b6d", 282 "size": 229 283 } 284 ]`)), 285 Response: testnet.TestResponse{ 286 Status: http.StatusOK, 287 Body: matchedResources, 288 }, 289 } 290 291 var matchResourceRequestImbalanced = testnet.TestRequest{ 292 Method: "PUT", 293 Path: "/v2/resource_match", 294 Matcher: testnet.RequestBodyMatcher(testnet.RemoveWhiteSpaceFromBody(`[ 295 { 296 "sha1": "2474735f5163ba7612ef641f438f4b5bee00127b", 297 "size": 51 298 }, 299 { 300 "sha1": "345f999aef9070fb9a608e65cf221b7038156b6d", 301 "size": 229 302 } 303 ]`)), 304 Response: testnet.TestResponse{ 305 Status: http.StatusOK, 306 Body: matchedResources, 307 }, 308 } 309 310 var defaultZipCheck = func(zipReader *zip.Reader) { 311 Expect(len(zipReader.File)).To(Equal(2), "Wrong number of files in zip") 312 313 var expectedPermissionBits os.FileMode 314 if runtime.GOOS == "windows" { 315 expectedPermissionBits = 0111 316 } else { 317 expectedPermissionBits = 0755 318 } 319 320 Expect(zipReader.File[0].Name).To(Equal("app.rb")) 321 Expect(executableBits(zipReader.File[0].Mode())).To(Equal(executableBits(expectedPermissionBits))) 322 323 nextFile: 324 for _, f := range zipReader.File { 325 for _, expected := range expectedApplicationContent { 326 if f.Name == expected { 327 continue nextFile 328 } 329 } 330 Fail("Expected " + f.Name + " but did not find it") 331 } 332 } 333 334 var defaultRequests = []testnet.TestRequest{ 335 uploadApplicationRequest(defaultZipCheck), 336 createProgressEndpoint("running"), 337 createProgressEndpoint("finished"), 338 } 339 340 var expectedApplicationContent = []string{"app.rb", "config.ru"} 341 342 const maxMultipartResponseSizeInBytes = 4096 343 344 func uploadBodyMatcher(zipChecks func(zipReader *zip.Reader)) func(*http.Request) { 345 return func(request *http.Request) { 346 defer GinkgoRecover() 347 err := request.ParseMultipartForm(maxMultipartResponseSizeInBytes) 348 if err != nil { 349 Fail(fmt.Sprintf("Failed parsing multipart form %v", err)) 350 return 351 } 352 defer request.MultipartForm.RemoveAll() 353 354 Expect(len(request.MultipartForm.Value)).To(Equal(1), "Should have 1 value") 355 valuePart, ok := request.MultipartForm.Value["resources"] 356 Expect(ok).To(BeTrue(), "Resource manifest not present") 357 Expect(len(valuePart)).To(Equal(1), "Wrong number of values") 358 359 resourceManifest := valuePart[0] 360 chompedResourceManifest := strings.Replace(resourceManifest, "\n", "", -1) 361 Expect(chompedResourceManifest).To(Equal(unmatchedResources), "Resources do not match") 362 363 Expect(len(request.MultipartForm.File)).To(Equal(1), "Wrong number of files") 364 365 fileHeaders, ok := request.MultipartForm.File["application"] 366 Expect(ok).To(BeTrue(), "Application file part not present") 367 Expect(len(fileHeaders)).To(Equal(1), "Wrong number of files") 368 369 applicationFile := fileHeaders[0] 370 Expect(applicationFile.Filename).To(Equal("application.zip"), "Wrong file name") 371 372 file, err := applicationFile.Open() 373 if err != nil { 374 Fail(fmt.Sprintf("Cannot get multipart file %v", err.Error())) 375 return 376 } 377 378 length, err := strconv.ParseInt(applicationFile.Header.Get("content-length"), 10, 64) 379 if err != nil { 380 Fail(fmt.Sprintf("Cannot convert content-length to int %v", err.Error())) 381 return 382 } 383 384 if zipChecks != nil { 385 zipReader, err := zip.NewReader(file, length) 386 if err != nil { 387 Fail(fmt.Sprintf("Error reading zip content %v", err.Error())) 388 return 389 } 390 391 zipChecks(zipReader) 392 } 393 } 394 } 395 396 func createProgressEndpoint(status string) (req testnet.TestRequest) { 397 body := fmt.Sprintf(` 398 { 399 "entity":{ 400 "status":"%s" 401 } 402 }`, status) 403 404 req.Method = "GET" 405 req.Path = "/v2/jobs/my-job-guid" 406 req.Response = testnet.TestResponse{ 407 Status: http.StatusCreated, 408 Body: body, 409 } 410 411 return 412 } 413 414 var matchExcludedResourceRequest = testnet.TestRequest{ 415 Method: "PUT", 416 Path: "/v2/resource_match", 417 Matcher: testnet.RequestBodyMatcher(testnet.RemoveWhiteSpaceFromBody(`[ 418 { 419 "fn": ".svn", 420 "sha1": "0", 421 "size": 0 422 }, 423 { 424 "fn": ".svn/test", 425 "sha1": "456b1d3f7cfbadc66d390de79cbbb6e6a10662da", 426 "size": 12 427 }, 428 { 429 "fn": "_darcs", 430 "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", 431 "size": 4 432 } 433 ]`)), 434 Response: testnet.TestResponse{ 435 Status: http.StatusOK, 436 Body: matchedResources, 437 }, 438 } 439 440 func executableBits(mode os.FileMode) os.FileMode { 441 return mode & 0111 442 }