github.com/asifdxtreme/cli@v6.1.3-0.20150123051144-9ead8700b4ae+incompatible/cf/api/application_bits/application_bits_test.go (about) 1 package application_bits_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 "github.com/cloudfoundry/cli/cf/api/fakes" 17 "github.com/cloudfoundry/cli/cf/api/resources" 18 "github.com/cloudfoundry/cli/cf/configuration/core_config" 19 "github.com/cloudfoundry/cli/cf/net" 20 testconfig "github.com/cloudfoundry/cli/testhelpers/configuration" 21 testnet "github.com/cloudfoundry/cli/testhelpers/net" 22 testterm "github.com/cloudfoundry/cli/testhelpers/terminal" 23 24 . "github.com/cloudfoundry/cli/cf/api/application_bits" 25 . "github.com/onsi/ginkgo" 26 . "github.com/onsi/gomega" 27 ) 28 29 var _ = Describe("CloudControllerApplicationBitsRepository", func() { 30 var ( 31 fixturesDir string 32 repo ApplicationBitsRepository 33 file1 resources.AppFileResource 34 file2 resources.AppFileResource 35 file3 resources.AppFileResource 36 file4 resources.AppFileResource 37 testHandler *testnet.TestHandler 38 testServer *httptest.Server 39 configRepo core_config.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, &testterm.FakeUI{}) 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} 57 file4 = resources.AppFileResource{Path: "Gemfile.lock", Sha1: "345f999aef9070fb9a608e65cf221b7038156b6d", Size: 229} 58 }) 59 60 setupTestServer := func(reqs ...testnet.TestRequest) { 61 testServer, testHandler = 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 }) 212 213 var matchedResources = testnet.RemoveWhiteSpaceFromBody(`[ 214 { 215 "fn": "Gemfile", 216 "sha1": "d9c3a51de5c89c11331d3b90b972789f1a14699a", 217 "size": 59 218 }, 219 { 220 "fn": "Gemfile.lock", 221 "sha1": "345f999aef9070fb9a608e65cf221b7038156b6d", 222 "size": 229 223 } 224 ]`) 225 226 var unmatchedResources = testnet.RemoveWhiteSpaceFromBody(`[ 227 { 228 "fn": "app.rb", 229 "sha1": "2474735f5163ba7612ef641f438f4b5bee00127b", 230 "size": 51 231 }, 232 { 233 "fn": "config.ru", 234 "sha1": "f097424ce1fa66c6cb9f5e8a18c317376ec12e05", 235 "size": 70 236 } 237 ]`) 238 239 func uploadApplicationRequest(zipCheck func(*zip.Reader)) testnet.TestRequest { 240 return testapi.NewCloudControllerTestRequest(testnet.TestRequest{ 241 Method: "PUT", 242 Path: "/v2/apps/my-cool-app-guid/bits", 243 Matcher: uploadBodyMatcher(zipCheck), 244 Response: testnet.TestResponse{ 245 Status: http.StatusCreated, 246 Body: ` 247 { 248 "metadata":{ 249 "guid": "my-job-guid", 250 "url": "/v2/jobs/my-job-guid" 251 } 252 } 253 `}, 254 }) 255 } 256 257 var matchResourceRequest = testnet.TestRequest{ 258 Method: "PUT", 259 Path: "/v2/resource_match", 260 Matcher: testnet.RequestBodyMatcher(testnet.RemoveWhiteSpaceFromBody(`[ 261 { 262 "fn": "app.rb", 263 "sha1": "2474735f5163ba7612ef641f438f4b5bee00127b", 264 "size": 51 265 }, 266 { 267 "fn": "config.ru", 268 "sha1": "f097424ce1fa66c6cb9f5e8a18c317376ec12e05", 269 "size": 70 270 }, 271 { 272 "fn": "Gemfile", 273 "sha1": "d9c3a51de5c89c11331d3b90b972789f1a14699a", 274 "size": 59 275 }, 276 { 277 "fn": "Gemfile.lock", 278 "sha1": "345f999aef9070fb9a608e65cf221b7038156b6d", 279 "size": 229 280 } 281 ]`)), 282 Response: testnet.TestResponse{ 283 Status: http.StatusOK, 284 Body: matchedResources, 285 }, 286 } 287 288 var defaultZipCheck = func(zipReader *zip.Reader) { 289 Expect(len(zipReader.File)).To(Equal(2), "Wrong number of files in zip") 290 291 var expectedPermissionBits os.FileMode 292 if runtime.GOOS == "windows" { 293 expectedPermissionBits = 0111 294 } else { 295 expectedPermissionBits = 0755 296 } 297 298 Expect(zipReader.File[0].Name).To(Equal("app.rb")) 299 Expect(executableBits(zipReader.File[0].Mode())).To(Equal(executableBits(expectedPermissionBits))) 300 301 nextFile: 302 for _, f := range zipReader.File { 303 for _, expected := range expectedApplicationContent { 304 if f.Name == expected { 305 continue nextFile 306 } 307 } 308 Fail("Expected " + f.Name + " but did not find it") 309 } 310 } 311 312 var defaultRequests = []testnet.TestRequest{ 313 uploadApplicationRequest(defaultZipCheck), 314 createProgressEndpoint("running"), 315 createProgressEndpoint("finished"), 316 } 317 318 var expectedApplicationContent = []string{"app.rb", "config.ru"} 319 320 const maxMultipartResponseSizeInBytes = 4096 321 322 func uploadBodyMatcher(zipChecks func(zipReader *zip.Reader)) func(*http.Request) { 323 return func(request *http.Request) { 324 defer GinkgoRecover() 325 err := request.ParseMultipartForm(maxMultipartResponseSizeInBytes) 326 if err != nil { 327 Fail(fmt.Sprintf("Failed parsing multipart form %v", err)) 328 return 329 } 330 defer request.MultipartForm.RemoveAll() 331 332 Expect(len(request.MultipartForm.Value)).To(Equal(1), "Should have 1 value") 333 valuePart, ok := request.MultipartForm.Value["resources"] 334 Expect(ok).To(BeTrue(), "Resource manifest not present") 335 Expect(len(valuePart)).To(Equal(1), "Wrong number of values") 336 337 resourceManifest := valuePart[0] 338 chompedResourceManifest := strings.Replace(resourceManifest, "\n", "", -1) 339 Expect(chompedResourceManifest).To(Equal(unmatchedResources), "Resources do not match") 340 341 Expect(len(request.MultipartForm.File)).To(Equal(1), "Wrong number of files") 342 343 fileHeaders, ok := request.MultipartForm.File["application"] 344 Expect(ok).To(BeTrue(), "Application file part not present") 345 Expect(len(fileHeaders)).To(Equal(1), "Wrong number of files") 346 347 applicationFile := fileHeaders[0] 348 Expect(applicationFile.Filename).To(Equal("application.zip"), "Wrong file name") 349 350 file, err := applicationFile.Open() 351 if err != nil { 352 Fail(fmt.Sprintf("Cannot get multipart file %v", err.Error())) 353 return 354 } 355 356 length, err := strconv.ParseInt(applicationFile.Header.Get("content-length"), 10, 64) 357 if err != nil { 358 Fail(fmt.Sprintf("Cannot convert content-length to int %v", err.Error())) 359 return 360 } 361 362 if zipChecks != nil { 363 zipReader, err := zip.NewReader(file, length) 364 if err != nil { 365 Fail(fmt.Sprintf("Error reading zip content %v", err.Error())) 366 return 367 } 368 369 zipChecks(zipReader) 370 } 371 } 372 } 373 374 func createProgressEndpoint(status string) (req testnet.TestRequest) { 375 body := fmt.Sprintf(` 376 { 377 "entity":{ 378 "status":"%s" 379 } 380 }`, status) 381 382 req.Method = "GET" 383 req.Path = "/v2/jobs/my-job-guid" 384 req.Response = testnet.TestResponse{ 385 Status: http.StatusCreated, 386 Body: body, 387 } 388 389 return 390 } 391 392 var matchExcludedResourceRequest = testnet.TestRequest{ 393 Method: "PUT", 394 Path: "/v2/resource_match", 395 Matcher: testnet.RequestBodyMatcher(testnet.RemoveWhiteSpaceFromBody(`[ 396 { 397 "fn": ".svn", 398 "sha1": "0", 399 "size": 0 400 }, 401 { 402 "fn": ".svn/test", 403 "sha1": "456b1d3f7cfbadc66d390de79cbbb6e6a10662da", 404 "size": 12 405 }, 406 { 407 "fn": "_darcs", 408 "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", 409 "size": 4 410 } 411 ]`)), 412 Response: testnet.TestResponse{ 413 Status: http.StatusOK, 414 Body: matchedResources, 415 }, 416 } 417 418 func executableBits(mode os.FileMode) os.FileMode { 419 return mode & 0111 420 }