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  }