github.com/paketo-buildpacks/libpak@v1.70.0/layer_test.go (about)

     1  /*
     2   * Copyright 2018-2020 the original author or 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   *      https://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 libpak_test
    18  
    19  import (
    20  	"fmt"
    21  	"net/http"
    22  	"os"
    23  	"path/filepath"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/buildpacks/libcnb"
    28  	. "github.com/onsi/gomega"
    29  	"github.com/onsi/gomega/ghttp"
    30  	"github.com/sclevine/spec"
    31  
    32  	"github.com/paketo-buildpacks/libpak"
    33  	"github.com/paketo-buildpacks/libpak/bard"
    34  )
    35  
    36  func testLayer(t *testing.T, context spec.G, it spec.S) {
    37  	var (
    38  		Expect = NewWithT(t).Expect
    39  
    40  		layersDir string
    41  		layer     libcnb.Layer
    42  	)
    43  
    44  	it.Before(func() {
    45  		layersDir = t.TempDir()
    46  		layer.Path = filepath.Join(layersDir, "test-layer")
    47  
    48  		layer.Exec.Path = layer.Path
    49  		layer.Metadata = map[string]interface{}{}
    50  		layer.Profile = libcnb.Profile{}
    51  	})
    52  
    53  	it.After(func() {
    54  		Expect(os.RemoveAll(layersDir)).To(Succeed())
    55  	})
    56  
    57  	context("LayerContributor", func() {
    58  		var (
    59  			lc libpak.LayerContributor
    60  		)
    61  
    62  		it.Before(func() {
    63  			lc.ExpectedMetadata = map[string]interface{}{
    64  				"alpha": "test-alpha",
    65  				"bravo": map[string]interface{}{
    66  					"bravo-1": "test-bravo-1",
    67  					"bravo-2": "test-bravo-2",
    68  				},
    69  			}
    70  		})
    71  
    72  		it("calls function with no existing metadata", func() {
    73  			var called bool
    74  
    75  			_, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
    76  				called = true
    77  				return layer, nil
    78  			})
    79  			Expect(err).NotTo(HaveOccurred())
    80  
    81  			Expect(called).To(BeTrue())
    82  		})
    83  
    84  		it("calls function with non-matching metadata", func() {
    85  			layer.Metadata["alpha"] = "test-alpha"
    86  
    87  			var called bool
    88  
    89  			_, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
    90  				called = true
    91  				return layer, nil
    92  			})
    93  			Expect(err).NotTo(HaveOccurred())
    94  
    95  			Expect(called).To(BeTrue())
    96  		})
    97  
    98  		context("reloads layers not restored", func() {
    99  			var called bool
   100  
   101  			it.Before(func() {
   102  				layer.Metadata = map[string]interface{}{
   103  					"alpha": "test-alpha",
   104  					"bravo": map[string]interface{}{
   105  						"bravo-1": "test-bravo-1",
   106  						"bravo-2": "test-bravo-2",
   107  					},
   108  				}
   109  			})
   110  
   111  			it("calls function with matching metadata but no layer directory on cache layer", func() {
   112  				Expect(os.WriteFile(fmt.Sprintf("%s.toml", layer.Path), []byte{}, 0644)).To(Succeed())
   113  				Expect(os.RemoveAll(layer.Path)).To(Succeed())
   114  				lc.ExpectedTypes.Cache = true
   115  
   116  				_, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   117  					called = true
   118  					return layer, nil
   119  				})
   120  				Expect(err).NotTo(HaveOccurred())
   121  
   122  				Expect(called).To(BeTrue())
   123  			})
   124  
   125  			it("calls function with matching metadata but no layer directory on build layer", func() {
   126  				Expect(os.WriteFile(fmt.Sprintf("%s.toml", layer.Path), []byte{}, 0644)).To(Succeed())
   127  				Expect(os.RemoveAll(layer.Path)).To(Succeed())
   128  				lc.ExpectedTypes.Build = true
   129  
   130  				_, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   131  					called = true
   132  					return layer, nil
   133  				})
   134  				Expect(err).NotTo(HaveOccurred())
   135  
   136  				Expect(called).To(BeTrue())
   137  			})
   138  
   139  			it("calls function with matching metadata but an empty layer directory on build layer", func() {
   140  				Expect(os.WriteFile(fmt.Sprintf("%s.toml", layer.Path), []byte{}, 0644)).To(Succeed())
   141  				Expect(os.MkdirAll(layer.Path, 0755)).To(Succeed())
   142  				lc.ExpectedTypes.Build = true
   143  
   144  				_, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   145  					called = true
   146  					return layer, nil
   147  				})
   148  				Expect(err).NotTo(HaveOccurred())
   149  
   150  				Expect(called).To(BeTrue())
   151  			})
   152  
   153  			it("does not call function with matching metadata when layer directory exists and has a file in it", func() {
   154  				Expect(os.WriteFile(fmt.Sprintf("%s.toml", layer.Path), []byte{}, 0644)).To(Succeed())
   155  				Expect(os.MkdirAll(layer.Path, 0755)).To(Succeed())
   156  				Expect(os.WriteFile(filepath.Join(layer.Path, "foo"), []byte{}, 0644)).To(Succeed())
   157  				lc.ExpectedTypes.Build = true
   158  
   159  				_, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   160  					called = true
   161  					return layer, nil
   162  				})
   163  				Expect(err).NotTo(HaveOccurred())
   164  
   165  				Expect(called).To(BeFalse())
   166  			})
   167  
   168  			it("does not call function with matching metadata when layer TOML missing", func() {
   169  				Expect(os.MkdirAll(layer.Path, 0755)).To(Succeed())
   170  				layer.Build = true
   171  
   172  				_, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   173  					called = true
   174  					return layer, nil
   175  				})
   176  				Expect(err).NotTo(HaveOccurred())
   177  
   178  				Expect(called).To(BeFalse())
   179  			})
   180  		})
   181  
   182  		it("does not call function with matching metadata", func() {
   183  			layer.Metadata = map[string]interface{}{
   184  				"alpha": "test-alpha",
   185  				"bravo": map[string]interface{}{
   186  					"bravo-1": "test-bravo-1",
   187  					"bravo-2": "test-bravo-2",
   188  				},
   189  			}
   190  
   191  			var called bool
   192  
   193  			_, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   194  				called = true
   195  				return layer, nil
   196  			})
   197  			Expect(err).NotTo(HaveOccurred())
   198  
   199  			Expect(called).To(BeFalse())
   200  		})
   201  
   202  		it("returns function error", func() {
   203  			_, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   204  				return libcnb.Layer{}, fmt.Errorf("test-error")
   205  			})
   206  			Expect(err).To(MatchError("test-error"))
   207  		})
   208  
   209  		it("adds expected metadata to layer", func() {
   210  			layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   211  				return layer, nil
   212  			})
   213  			Expect(err).NotTo(HaveOccurred())
   214  
   215  			Expect(layer.Metadata).To(Equal(map[string]interface{}{
   216  				"alpha": "test-alpha",
   217  				"bravo": map[string]interface{}{
   218  					"bravo-1": "test-bravo-1",
   219  					"bravo-2": "test-bravo-2",
   220  				},
   221  			}))
   222  		})
   223  
   224  		it("sets build layer flag", func() {
   225  			lc.ExpectedTypes.Build = true
   226  			layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   227  				return layer, nil
   228  			})
   229  			Expect(err).NotTo(HaveOccurred())
   230  
   231  			Expect(layer.LayerTypes.Build).To(BeTrue())
   232  		})
   233  
   234  		it("sets cache layer flag", func() {
   235  			lc.ExpectedTypes.Cache = true
   236  			layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   237  				return layer, nil
   238  			})
   239  			Expect(err).NotTo(HaveOccurred())
   240  
   241  			Expect(layer.LayerTypes.Cache).To(BeTrue())
   242  		})
   243  
   244  		it("sets launch layer flag", func() {
   245  			lc.ExpectedTypes.Launch = true
   246  			layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   247  				return layer, nil
   248  			})
   249  			Expect(err).NotTo(HaveOccurred())
   250  
   251  			Expect(layer.LayerTypes.Launch).To(BeTrue())
   252  		})
   253  
   254  		it("sets layer flags regardless of caching behavior (required for 0.6 API)", func() {
   255  			lc.ExpectedTypes.Launch = true
   256  			lc.ExpectedTypes.Cache = true
   257  			lc.ExpectedTypes.Build = true
   258  
   259  			layer.Metadata = map[string]interface{}{
   260  				"alpha": "test-alpha",
   261  				"bravo": map[string]interface{}{
   262  					"bravo-1": "test-bravo-1",
   263  					"bravo-2": "test-bravo-2",
   264  				},
   265  			}
   266  
   267  			var called bool
   268  
   269  			layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) {
   270  				called = true
   271  				return layer, nil
   272  			})
   273  			Expect(err).NotTo(HaveOccurred())
   274  			Expect(called).To(BeFalse())
   275  
   276  			Expect(layer.LayerTypes.Launch).To(BeTrue())
   277  			Expect(layer.LayerTypes.Cache).To(BeTrue())
   278  			Expect(layer.LayerTypes.Build).To(BeTrue())
   279  		})
   280  	})
   281  
   282  	context("NewDependencyLayer", func() {
   283  		var dep libpak.BuildpackDependency
   284  
   285  		it.Before(func() {
   286  			dep = libpak.BuildpackDependency{
   287  				ID:      "test-id",
   288  				Name:    "test-name",
   289  				Version: "1.1.1",
   290  				URI:     "test-uri",
   291  				SHA256:  "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1",
   292  				Stacks:  []string{"test-stack"},
   293  				Licenses: []libpak.BuildpackDependencyLicense{
   294  					{
   295  						Type: "test-type",
   296  						URI:  "test-uri",
   297  					},
   298  				},
   299  			}
   300  		})
   301  
   302  		it("returns a BOM entry for the layer", func() {
   303  			_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{})
   304  			Expect(entry.Name).To(Equal("test-id"))
   305  			Expect(entry.Metadata["name"]).To(Equal("test-name"))
   306  			Expect(entry.Metadata["version"]).To(Equal("1.1.1"))
   307  			Expect(entry.Metadata["uri"]).To(Equal("test-uri"))
   308  			Expect(entry.Metadata["sha256"]).To(Equal("576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1"))
   309  			Expect(entry.Metadata["licenses"]).To(Equal([]libpak.BuildpackDependencyLicense{
   310  				{
   311  					Type: "test-type",
   312  					URI:  "test-uri",
   313  				},
   314  			}))
   315  		})
   316  
   317  		context("launch layer type", func() {
   318  			it("only sets launch on the entry", func() {
   319  				_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{
   320  					Launch: true,
   321  				})
   322  				Expect(entry.Launch).To(BeTrue())
   323  				Expect(entry.Build).To(BeFalse())
   324  			})
   325  		})
   326  
   327  		context("launch and build layer type", func() {
   328  			it("sets launch and build on the entry", func() {
   329  				_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{
   330  					Launch: true,
   331  					Build:  true,
   332  				})
   333  				Expect(entry.Launch).To(BeTrue())
   334  				Expect(entry.Build).To(BeTrue())
   335  			})
   336  		})
   337  
   338  		context("launch and cache layer type", func() {
   339  			it("sets launch and build on the entry", func() {
   340  				_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{
   341  					Launch: true,
   342  					Cache:  true,
   343  				})
   344  				Expect(entry.Launch).To(BeTrue())
   345  				Expect(entry.Build).To(BeTrue())
   346  			})
   347  		})
   348  
   349  		context("build layer type", func() {
   350  			it("sets build on the entry", func() {
   351  				_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{
   352  					Build: true,
   353  				})
   354  				Expect(entry.Launch).To(BeFalse())
   355  				Expect(entry.Build).To(BeTrue())
   356  			})
   357  		})
   358  
   359  		context("cache layer type", func() {
   360  			it("sets build on the entry", func() {
   361  				_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{
   362  					Cache: true,
   363  				})
   364  				Expect(entry.Launch).To(BeFalse())
   365  				Expect(entry.Build).To(BeTrue())
   366  			})
   367  		})
   368  
   369  		context("no layer types", func() {
   370  			it("sets build on the entry", func() {
   371  				_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{})
   372  				Expect(entry.Launch).To(BeFalse())
   373  				Expect(entry.Build).To(BeTrue())
   374  			})
   375  		})
   376  	})
   377  
   378  	context("DependencyLayerContributor", func() {
   379  		var (
   380  			dependency libpak.BuildpackDependency
   381  			dlc        libpak.DependencyLayerContributor
   382  			server     *ghttp.Server
   383  		)
   384  
   385  		it.Before(func() {
   386  			RegisterTestingT(t)
   387  			server = ghttp.NewServer()
   388  
   389  			deprecationDate, err := time.Parse(time.RFC3339, "2021-04-01T00:00:00Z")
   390  			Expect(err).ToNot(HaveOccurred())
   391  
   392  			dependency = libpak.BuildpackDependency{
   393  				ID:      "test-id",
   394  				Name:    "test-name",
   395  				Version: "1.1.1",
   396  				URI:     fmt.Sprintf("%s/test-path", server.URL()),
   397  				SHA256:  "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1",
   398  				Stacks:  []string{"test-stack"},
   399  				Licenses: []libpak.BuildpackDependencyLicense{
   400  					{
   401  						Type: "test-type",
   402  						URI:  "test-uri",
   403  					},
   404  				},
   405  				CPEs:            []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   406  				PURL:            "pkg:generic/some-java11@11.0.2?arch=amd64",
   407  				DeprecationDate: deprecationDate,
   408  			}
   409  
   410  			layer.Metadata = map[string]interface{}{}
   411  
   412  			dlc.ExpectedMetadata = dependency
   413  			dlc.Dependency = dependency
   414  			dlc.DependencyCache.CachePath = layer.Path
   415  			dlc.DependencyCache.DownloadPath = layer.Path
   416  		})
   417  
   418  		it.After(func() {
   419  			server.Close()
   420  		})
   421  
   422  		it("calls function with no existing metadata", func() {
   423  			server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture"))
   424  
   425  			var called bool
   426  
   427  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   428  				defer artifact.Close()
   429  
   430  				called = true
   431  				return layer, nil
   432  			})
   433  			Expect(err).NotTo(HaveOccurred())
   434  
   435  			Expect(called).To(BeTrue())
   436  		})
   437  
   438  		it("modifies request", func() {
   439  			server.AppendHandlers(ghttp.CombineHandlers(
   440  				ghttp.VerifyHeaderKV("Test-Key", "test-value"),
   441  				ghttp.RespondWith(http.StatusOK, "test-fixture"),
   442  			))
   443  
   444  			dlc.RequestModifierFuncs = append(dlc.RequestModifierFuncs, func(request *http.Request) (*http.Request, error) {
   445  				request.Header.Add("Test-Key", "test-value")
   446  				return request, nil
   447  			})
   448  
   449  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   450  				defer artifact.Close()
   451  				return layer, nil
   452  			})
   453  			Expect(err).NotTo(HaveOccurred())
   454  		})
   455  
   456  		it("calls function with non-matching metadata", func() {
   457  			layer.Metadata["alpha"] = "test-alpha"
   458  
   459  			server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture"))
   460  
   461  			var called bool
   462  
   463  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   464  				defer artifact.Close()
   465  
   466  				called = true
   467  				return layer, nil
   468  			})
   469  			Expect(err).NotTo(HaveOccurred())
   470  
   471  			Expect(called).To(BeTrue())
   472  		})
   473  
   474  		it("does not call function with matching metadata", func() {
   475  			layer.Metadata = map[string]interface{}{
   476  				"id":      dependency.ID,
   477  				"name":    dependency.Name,
   478  				"version": dependency.Version,
   479  				"uri":     dependency.URI,
   480  				"sha256":  dependency.SHA256,
   481  				"stacks":  []interface{}{dependency.Stacks[0]},
   482  				"licenses": []map[string]interface{}{
   483  					{
   484  						"type": dependency.Licenses[0].Type,
   485  						"uri":  dependency.Licenses[0].URI,
   486  					},
   487  				},
   488  				"cpes":             []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   489  				"purl":             "pkg:generic/some-java11@11.0.2?arch=amd64",
   490  				"deprecation_date": dependency.DeprecationDate,
   491  			}
   492  
   493  			var called bool
   494  
   495  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   496  				defer artifact.Close()
   497  
   498  				called = true
   499  				return layer, nil
   500  			})
   501  			Expect(err).NotTo(HaveOccurred())
   502  
   503  			Expect(called).To(BeFalse())
   504  		})
   505  
   506  		it("does not call function with non-matching deprecation_date format", func() {
   507  			dependency = libpak.BuildpackDependency{
   508  				ID:      "test-id",
   509  				Name:    "test-name",
   510  				Version: "1.1.1",
   511  				URI:     fmt.Sprintf("%s/test-path", server.URL()),
   512  				SHA256:  "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1",
   513  				Stacks:  []string{"test-stack"},
   514  				Licenses: []libpak.BuildpackDependencyLicense{
   515  					{
   516  						Type: "test-type",
   517  						URI:  "test-uri",
   518  					},
   519  				},
   520  				CPEs:            []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   521  				PURL:            "pkg:generic/some-java11@11.0.2?arch=amd64",
   522  				DeprecationDate: dependency.DeprecationDate, // parsed as '2021-04-01 00:00:00 +0000 UTC'
   523  			}
   524  			dlc.ExpectedMetadata = map[string]interface{}{"dependency": dependency}
   525  
   526  			layer.Metadata = map[string]interface{}{"dependency": map[string]interface{}{
   527  				"id":      dependency.ID,
   528  				"name":    dependency.Name,
   529  				"version": dependency.Version,
   530  				"uri":     dependency.URI,
   531  				"sha256":  dependency.SHA256,
   532  				"stacks":  []interface{}{dependency.Stacks[0]},
   533  				"licenses": []map[string]interface{}{
   534  					{
   535  						"type": dependency.Licenses[0].Type,
   536  						"uri":  dependency.Licenses[0].URI,
   537  					},
   538  				},
   539  				"cpes":             []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   540  				"purl":             "pkg:generic/some-java11@11.0.2?arch=amd64",
   541  				"deprecation_date": "2021-04-01T00:00:00Z", // does not match without truncation
   542  			}}
   543  
   544  			var called bool
   545  
   546  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   547  				defer artifact.Close()
   548  
   549  				called = true
   550  				return layer, nil
   551  			})
   552  			Expect(err).NotTo(HaveOccurred())
   553  
   554  			Expect(called).To(BeFalse())
   555  		})
   556  
   557  		it("gracefully handles a deprecationDate in time.Time format in actual layer metadata", func() {
   558  			// reusing It: does not call function with non-matching deprecation_date format
   559  			// but this time with a deprecationDate formatted as time.Time in the actual layer metadata
   560  			actualDeprecationDate, _ := time.Parse(time.RFC3339, "2021-04-01T00:00:00Z")
   561  
   562  			dependency = libpak.BuildpackDependency{
   563  				ID:      "test-id",
   564  				Name:    "test-name",
   565  				Version: "1.1.1",
   566  				URI:     fmt.Sprintf("%s/test-path", server.URL()),
   567  				SHA256:  "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1",
   568  				Stacks:  []string{"test-stack"},
   569  				Licenses: []libpak.BuildpackDependencyLicense{
   570  					{
   571  						Type: "test-type",
   572  						URI:  "test-uri",
   573  					},
   574  				},
   575  				CPEs:            []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   576  				PURL:            "pkg:generic/some-java11@11.0.2?arch=amd64",
   577  				DeprecationDate: dependency.DeprecationDate, // parsed as '2021-04-01 00:00:00 +0000 UTC'
   578  			}
   579  			dlc.ExpectedMetadata = map[string]interface{}{"dependency": dependency}
   580  
   581  			layer.Metadata = map[string]interface{}{"dependency": map[string]interface{}{
   582  				"id":      dependency.ID,
   583  				"name":    dependency.Name,
   584  				"version": dependency.Version,
   585  				"uri":     dependency.URI,
   586  				"sha256":  dependency.SHA256,
   587  				"stacks":  []interface{}{dependency.Stacks[0]},
   588  				"licenses": []map[string]interface{}{
   589  					{
   590  						"type": dependency.Licenses[0].Type,
   591  						"uri":  dependency.Licenses[0].URI,
   592  					},
   593  				},
   594  				"cpes":             []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   595  				"purl":             "pkg:generic/some-java11@11.0.2?arch=amd64",
   596  				"deprecation_date": actualDeprecationDate, // does not match without truncation
   597  			}}
   598  
   599  			var called bool
   600  
   601  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   602  				defer artifact.Close()
   603  
   604  				called = true
   605  				return layer, nil
   606  			})
   607  			Expect(err).NotTo(HaveOccurred())
   608  
   609  			Expect(called).To(BeFalse())
   610  		})
   611  
   612  		it("does not panic on unsupported deprecationDate format in layer metadata", func() {
   613  			// Unexpected type (not string or time.Time)
   614  			actualDeprecationDate := 1234
   615  
   616  			dependency = libpak.BuildpackDependency{
   617  				ID:              "test-id",
   618  				DeprecationDate: dependency.DeprecationDate, // parsed as '2021-04-01 00:00:00 +0000 UTC'
   619  			}
   620  			dlc.ExpectedMetadata = map[string]interface{}{"dependency": dependency}
   621  
   622  			layer.Metadata = map[string]interface{}{"dependency": map[string]interface{}{
   623  				"id":               dependency.ID,
   624  				"deprecation_date": actualDeprecationDate, // does not match without truncation
   625  			}}
   626  
   627  			var called bool
   628  
   629  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   630  				defer artifact.Close()
   631  
   632  				called = true
   633  				return layer, nil
   634  			})
   635  			Expect(err).To(MatchError(ContainSubstring("unexpected type int for deprecation_date")))
   636  			Expect(called).To(BeFalse())
   637  		})
   638  
   639  		it("does not contribute when deprecation_date is found on metadata map root", func() {
   640  			dependency = libpak.BuildpackDependency{
   641  				ID:      "test-id",
   642  				Name:    "test-name",
   643  				Version: "1.1.1",
   644  				URI:     fmt.Sprintf("%s/test-path", server.URL()),
   645  				SHA256:  "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1",
   646  				Stacks:  []string{"test-stack"},
   647  				Licenses: []libpak.BuildpackDependencyLicense{
   648  					{
   649  						Type: "test-type",
   650  						URI:  "test-uri",
   651  					},
   652  				},
   653  				CPEs: []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   654  				PURL: "pkg:generic/some-java11@11.0.2?arch=amd64",
   655  			}
   656  			dlc.ExpectedMetadata = dependency
   657  
   658  			layer.Metadata = map[string]interface{}{
   659  				"id":      dependency.ID,
   660  				"name":    dependency.Name,
   661  				"version": dependency.Version,
   662  				"uri":     dependency.URI,
   663  				"sha256":  dependency.SHA256,
   664  				"stacks":  []interface{}{dependency.Stacks[0]},
   665  				"licenses": []map[string]interface{}{
   666  					{
   667  						"type": dependency.Licenses[0].Type,
   668  						"uri":  dependency.Licenses[0].URI,
   669  					},
   670  				},
   671  				"cpes":             []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   672  				"purl":             "pkg:generic/some-java11@11.0.2?arch=amd64",
   673  				"deprecation_date": "0001-01-01T00:00:00Z",
   674  			}
   675  
   676  			var called bool
   677  
   678  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   679  				defer artifact.Close()
   680  
   681  				called = true
   682  				return layer, nil
   683  			})
   684  			Expect(err).NotTo(HaveOccurred())
   685  
   686  			Expect(called).To(BeFalse())
   687  		})
   688  
   689  		it("does not call function with missing deprecation_date", func() {
   690  			dependency = libpak.BuildpackDependency{
   691  				ID:      "test-id",
   692  				Name:    "test-name",
   693  				Version: "1.1.1",
   694  				URI:     fmt.Sprintf("%s/test-path", server.URL()),
   695  				SHA256:  "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1",
   696  				Stacks:  []string{"test-stack"},
   697  				Licenses: []libpak.BuildpackDependencyLicense{
   698  					{
   699  						Type: "test-type",
   700  						URI:  "test-uri",
   701  					},
   702  				},
   703  				CPEs: []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   704  				PURL: "pkg:generic/some-java11@11.0.2?arch=amd64",
   705  			}
   706  			dlc.ExpectedMetadata = map[string]interface{}{"dependency": dependency}
   707  
   708  			layer.Metadata = map[string]interface{}{"dependency": map[string]interface{}{
   709  				"id":      dependency.ID,
   710  				"name":    dependency.Name,
   711  				"version": dependency.Version,
   712  				"uri":     dependency.URI,
   713  				"sha256":  dependency.SHA256,
   714  				"stacks":  []interface{}{dependency.Stacks[0]},
   715  				"licenses": []map[string]interface{}{
   716  					{
   717  						"type": dependency.Licenses[0].Type,
   718  						"uri":  dependency.Licenses[0].URI,
   719  					},
   720  				},
   721  				"cpes":             []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   722  				"purl":             "pkg:generic/some-java11@11.0.2?arch=amd64",
   723  				"deprecation_date": "0001-01-01T00:00:00Z",
   724  			}}
   725  
   726  			var called bool
   727  
   728  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   729  				defer artifact.Close()
   730  
   731  				called = true
   732  				return layer, nil
   733  			})
   734  			Expect(err).NotTo(HaveOccurred())
   735  
   736  			Expect(called).To(BeFalse())
   737  		})
   738  
   739  		it("returns function error", func() {
   740  			server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture"))
   741  
   742  			_, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   743  				defer artifact.Close()
   744  
   745  				return libcnb.Layer{}, fmt.Errorf("test-error")
   746  			})
   747  			Expect(err).To(MatchError("test-error"))
   748  		})
   749  
   750  		it("adds expected metadata to layer", func() {
   751  			server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture"))
   752  
   753  			layer, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   754  				defer artifact.Close()
   755  				return layer, nil
   756  			})
   757  			Expect(err).NotTo(HaveOccurred())
   758  
   759  			Expect(layer.Metadata).To(Equal(map[string]interface{}{
   760  				"id":      dependency.ID,
   761  				"name":    dependency.Name,
   762  				"version": dependency.Version,
   763  				"uri":     dependency.URI,
   764  				"sha256":  dependency.SHA256,
   765  				"stacks":  []interface{}{dependency.Stacks[0]},
   766  				"licenses": []map[string]interface{}{
   767  					{
   768  						"type": dependency.Licenses[0].Type,
   769  						"uri":  dependency.Licenses[0].URI,
   770  					},
   771  				},
   772  				"cpes":             []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   773  				"purl":             "pkg:generic/some-java11@11.0.2?arch=amd64",
   774  				"deprecation_date": dependency.DeprecationDate,
   775  			}))
   776  		})
   777  
   778  		it("sets layer flags regardless of caching behavior (required for 0.6 API)", func() {
   779  			layer.Metadata = map[string]interface{}{
   780  				"id":      dependency.ID,
   781  				"name":    dependency.Name,
   782  				"version": dependency.Version,
   783  				"uri":     dependency.URI,
   784  				"sha256":  dependency.SHA256,
   785  				"stacks":  []interface{}{dependency.Stacks[0]},
   786  				"licenses": []map[string]interface{}{
   787  					{
   788  						"type": dependency.Licenses[0].Type,
   789  						"uri":  dependency.Licenses[0].URI,
   790  					},
   791  				},
   792  				"cpes":             []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
   793  				"purl":             "pkg:generic/some-java11@11.0.2?arch=amd64",
   794  				"deprecation_date": dependency.DeprecationDate,
   795  			}
   796  			dlc.ExpectedTypes.Launch = true
   797  			dlc.ExpectedTypes.Cache = true
   798  			dlc.ExpectedTypes.Build = true
   799  
   800  			var called bool
   801  
   802  			layer, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   803  				defer artifact.Close()
   804  
   805  				called = true
   806  				return layer, nil
   807  			})
   808  			Expect(err).NotTo(HaveOccurred())
   809  
   810  			Expect(called).To(BeFalse())
   811  
   812  			Expect(layer.LayerTypes.Launch).To(BeTrue())
   813  			Expect(layer.LayerTypes.Cache).To(BeTrue())
   814  			Expect(layer.LayerTypes.Build).To(BeTrue())
   815  		})
   816  
   817  		it("adds expected Syft SBOM file", func() {
   818  			server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture"))
   819  
   820  			layer, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
   821  				defer artifact.Close()
   822  				return layer, nil
   823  			})
   824  			Expect(err).NotTo(HaveOccurred())
   825  
   826  			outputFile := layer.SBOMPath(libcnb.SyftJSON)
   827  			Expect(outputFile).To(BeARegularFile())
   828  
   829  			data, err := os.ReadFile(outputFile)
   830  			Expect(err).ToNot(HaveOccurred())
   831  			Expect(string(data)).To(ContainSubstring(`"Artifacts":[`))
   832  			Expect(string(data)).To(ContainSubstring(`"FoundBy":"libpak",`))
   833  			Expect(string(data)).To(ContainSubstring(`"PURL":"pkg:generic/some-java11@11.0.2?arch=amd64"`))
   834  			Expect(string(data)).To(ContainSubstring(`"Schema":{`))
   835  			Expect(string(data)).To(ContainSubstring(`"Descriptor":{`))
   836  			Expect(string(data)).To(ContainSubstring(`"Source":{`))
   837  		})
   838  	})
   839  
   840  	context("NewHelperLayer", func() {
   841  		it("returns a BOM entry with version equal to buildpack version", func() {
   842  			_, entry := libpak.NewHelperLayer(libcnb.Buildpack{
   843  				API: "0.6",
   844  				Info: libcnb.BuildpackInfo{
   845  					Version: "test-version",
   846  				},
   847  			}, "test-name-1", "test-name-2")
   848  			Expect(entry).To(Equal(
   849  				libcnb.BOMEntry{
   850  					Name: filepath.Base("helper"),
   851  					Metadata: map[string]interface{}{
   852  						"layer":   "helper",
   853  						"names":   []string{"test-name-1", "test-name-2"},
   854  						"version": "test-version",
   855  					},
   856  					Launch: true,
   857  					Build:  false,
   858  				},
   859  			))
   860  		})
   861  
   862  		it("returns a BOM entry on API 0.7 too", func() {
   863  			_, entry := libpak.NewHelperLayer(libcnb.Buildpack{
   864  				API: "0.7",
   865  				Info: libcnb.BuildpackInfo{
   866  					Version: "test-version",
   867  				},
   868  			}, "test-name-1", "test-name-2")
   869  			Expect(entry).To(Equal(libcnb.BOMEntry{
   870  				Name: filepath.Base("helper"),
   871  				Metadata: map[string]interface{}{
   872  					"layer":   "helper",
   873  					"names":   []string{"test-name-1", "test-name-2"},
   874  					"version": "test-version",
   875  				},
   876  				Launch: true,
   877  				Build:  false,
   878  			}))
   879  		})
   880  	})
   881  
   882  	context("HelperLayerContributor", func() {
   883  		var (
   884  			buildpack libcnb.Buildpack
   885  			hlc       libpak.HelperLayerContributor
   886  		)
   887  
   888  		it.Before(func() {
   889  			buildpack.Info = libcnb.BuildpackInfo{
   890  				ID:       "test-id",
   891  				Name:     "test-name",
   892  				Version:  "test-version",
   893  				Homepage: "test-homepage",
   894  			}
   895  
   896  			buildpack.Path = t.TempDir()
   897  			file := filepath.Join(buildpack.Path, "bin")
   898  			Expect(os.MkdirAll(file, 0755)).To(Succeed())
   899  
   900  			file = filepath.Join(file, "helper")
   901  			Expect(os.WriteFile(file, []byte{}, 0755)).To(Succeed())
   902  
   903  			hlc = libpak.HelperLayerContributor{
   904  				Path:          file,
   905  				BuildpackInfo: buildpack.Info,
   906  				Logger:        bard.Logger{},
   907  				Names:         []string{"test-name-1", "test-name-2"},
   908  			}
   909  		})
   910  
   911  		it.After(func() {
   912  			Expect(os.RemoveAll(buildpack.Path)).To(Succeed())
   913  		})
   914  
   915  		it("calls function with no existing metadata", func() {
   916  			_, err := hlc.Contribute(layer)
   917  			Expect(err).NotTo(HaveOccurred())
   918  
   919  			Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).To(BeAnExistingFile())
   920  		})
   921  
   922  		it("calls function with non-matching metadata", func() {
   923  			layer.Metadata["alpha"] = "other-alpha"
   924  
   925  			_, err := hlc.Contribute(layer)
   926  			Expect(err).NotTo(HaveOccurred())
   927  
   928  			file := filepath.Join(layer.Exec.FilePath("test-name-1"))
   929  			Expect(file).To(BeAnExistingFile())
   930  			Expect(os.Readlink(file)).To(Equal(filepath.Join(layer.Path, "helper")))
   931  
   932  			file = filepath.Join(layer.Exec.FilePath("test-name-2"))
   933  			Expect(file).To(BeAnExistingFile())
   934  			Expect(os.Readlink(file)).To(Equal(filepath.Join(layer.Path, "helper")))
   935  		})
   936  
   937  		it("does not call function with matching metadata", func() {
   938  			buildpackInfo := map[string]interface{}{
   939  				"id":          buildpack.Info.ID,
   940  				"name":        buildpack.Info.Name,
   941  				"version":     buildpack.Info.Version,
   942  				"homepage":    buildpack.Info.Homepage,
   943  				"clear-env":   buildpack.Info.ClearEnvironment,
   944  				"description": "",
   945  			}
   946  			layer.Metadata["buildpackInfo"] = buildpackInfo
   947  			layer.Metadata["helperNames"] = []interface{}{hlc.Names[0], hlc.Names[1]}
   948  
   949  			_, err := hlc.Contribute(layer)
   950  
   951  			Expect(err).NotTo(HaveOccurred())
   952  
   953  			Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).NotTo(BeAnExistingFile())
   954  			Expect(filepath.Join(layer.Exec.FilePath("test-name-2"))).NotTo(BeAnExistingFile())
   955  		})
   956  
   957  		it("adds expected metadata to layer", func() {
   958  			layer, err := hlc.Contribute(layer)
   959  			Expect(err).NotTo(HaveOccurred())
   960  
   961  			buildpackInfo := map[string]interface{}{
   962  				"id":          buildpack.Info.ID,
   963  				"name":        buildpack.Info.Name,
   964  				"version":     buildpack.Info.Version,
   965  				"homepage":    buildpack.Info.Homepage,
   966  				"clear-env":   buildpack.Info.ClearEnvironment,
   967  				"description": "",
   968  			}
   969  			Expect(layer.Metadata).To(Equal(map[string]interface{}{"buildpackInfo": buildpackInfo, "helperNames": []interface{}{hlc.Names[0], hlc.Names[1]}}))
   970  		})
   971  
   972  		it("sets layer flags regardless of caching behavior (required for 0.6 API)", func() {
   973  			buildpackInfo := map[string]interface{}{
   974  				"id":          buildpack.Info.ID,
   975  				"name":        buildpack.Info.Name,
   976  				"version":     buildpack.Info.Version,
   977  				"homepage":    buildpack.Info.Homepage,
   978  				"clear-env":   buildpack.Info.ClearEnvironment,
   979  				"description": "",
   980  			}
   981  			layer.Metadata["buildpackInfo"] = buildpackInfo
   982  			layer.Metadata["helperNames"] = []interface{}{hlc.Names[0], hlc.Names[1]}
   983  
   984  			// Launch is the only one set & always true
   985  
   986  			layer, err := hlc.Contribute(layer)
   987  			Expect(err).NotTo(HaveOccurred())
   988  
   989  			Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).NotTo(BeAnExistingFile())
   990  			Expect(filepath.Join(layer.Exec.FilePath("test-name-2"))).NotTo(BeAnExistingFile())
   991  
   992  			Expect(layer.LayerTypes.Launch).To(BeTrue())
   993  			Expect(layer.LayerTypes.Cache).To(BeFalse())
   994  			Expect(layer.LayerTypes.Build).To(BeFalse())
   995  		})
   996  
   997  		it("adds expected Syft SBOM file", func() {
   998  			layer.Metadata = map[string]interface{}{}
   999  
  1000  			_, err := hlc.Contribute(layer)
  1001  			Expect(err).NotTo(HaveOccurred())
  1002  
  1003  			Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).To(BeAnExistingFile())
  1004  			Expect(filepath.Join(layer.Exec.FilePath("test-name-2"))).To(BeAnExistingFile())
  1005  
  1006  			outputFile := layer.SBOMPath(libcnb.SyftJSON)
  1007  			Expect(outputFile).To(BeARegularFile())
  1008  
  1009  			data, err := os.ReadFile(outputFile)
  1010  			Expect(err).ToNot(HaveOccurred())
  1011  			Expect(string(data)).To(ContainSubstring(`"Artifacts":[`))
  1012  			Expect(string(data)).To(ContainSubstring(`"FoundBy":"libpak",`))
  1013  			Expect(string(data)).To(ContainSubstring(`"PURL":"pkg:generic/test-id@test-version"`))
  1014  			Expect(string(data)).To(ContainSubstring(`"CPEs":["cpe:2.3:a:test-id:test-name-1:test-version:*:*:*:*:*:*:*","cpe:2.3:a:test-id:test-name-2:test-version:*:*:*:*:*:*:*"]`))
  1015  			Expect(string(data)).To(ContainSubstring(`"Schema":{`))
  1016  			Expect(string(data)).To(ContainSubstring(`"Descriptor":{`))
  1017  			Expect(string(data)).To(ContainSubstring(`"Source":{`))
  1018  		})
  1019  	})
  1020  }