github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/inspectimage/writer/structured_bom_format_test.go (about) 1 package writer_test 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "testing" 8 9 "github.com/buildpacks/lifecycle/buildpack" 10 "github.com/heroku/color" 11 "github.com/sclevine/spec" 12 "github.com/sclevine/spec/report" 13 14 "github.com/buildpacks/pack/internal/config" 15 "github.com/buildpacks/pack/internal/inspectimage" 16 "github.com/buildpacks/pack/internal/inspectimage/writer" 17 "github.com/buildpacks/pack/pkg/client" 18 "github.com/buildpacks/pack/pkg/dist" 19 "github.com/buildpacks/pack/pkg/logging" 20 h "github.com/buildpacks/pack/testhelpers" 21 ) 22 23 func TestStructuredBOMFormat(t *testing.T) { 24 color.Disable(true) 25 defer color.Disable(false) 26 spec.Run(t, "StructuredBOMFormat Writer", testStructuredBOMFormat, spec.Parallel(), spec.Report(report.Terminal{})) 27 } 28 29 func testStructuredBOMFormat(t *testing.T, when spec.G, it spec.S) { 30 var ( 31 assert = h.NewAssertionManager(t) 32 outBuf *bytes.Buffer 33 34 remoteInfo *client.ImageInfo 35 localInfo *client.ImageInfo 36 remoteWithExtensionInfo *client.ImageInfo 37 localWithExtensionInfo *client.ImageInfo 38 generalInfo inspectimage.GeneralInfo 39 logger *logging.LogWithWriters 40 ) 41 42 when("Print", func() { 43 it.Before(func() { 44 outBuf = bytes.NewBuffer(nil) 45 logger = logging.NewLogWithWriters(outBuf, outBuf) 46 remoteInfo = &client.ImageInfo{ 47 BOM: []buildpack.BOMEntry{ 48 { 49 Require: buildpack.Require{ 50 Name: "remote-require", 51 Version: "1.2.3", 52 Metadata: map[string]interface{}{ 53 "cool-remote": "beans", 54 }, 55 }, 56 Buildpack: buildpack.GroupElement{ 57 ID: "remote-buildpack", 58 Version: "remote-buildpack-version", 59 }, 60 }, 61 }, 62 } 63 localInfo = &client.ImageInfo{ 64 BOM: []buildpack.BOMEntry{ 65 { 66 Require: buildpack.Require{ 67 Name: "local-require", 68 Version: "4.5.6", 69 Metadata: map[string]interface{}{ 70 "cool-local": "beans", 71 }, 72 }, 73 Buildpack: buildpack.GroupElement{ 74 ID: "local-buildpack", 75 Version: "local-buildpack-version", 76 }, 77 }, 78 }, 79 } 80 81 remoteWithExtensionInfo = &client.ImageInfo{ 82 BOM: []buildpack.BOMEntry{ 83 { 84 Require: buildpack.Require{ 85 Name: "remote-require", 86 Version: "1.2.3", 87 Metadata: map[string]interface{}{ 88 "cool-remote": "beans", 89 }, 90 }, 91 Buildpack: buildpack.GroupElement{ 92 ID: "remote-buildpack", 93 Version: "remote-buildpack-version", 94 }, 95 }, 96 }, 97 } 98 localWithExtensionInfo = &client.ImageInfo{ 99 BOM: []buildpack.BOMEntry{ 100 { 101 Require: buildpack.Require{ 102 Name: "local-require", 103 Version: "4.5.6", 104 Metadata: map[string]interface{}{ 105 "cool-local": "beans", 106 }, 107 }, 108 Buildpack: buildpack.GroupElement{ 109 ID: "local-buildpack", 110 Version: "local-buildpack-version", 111 }, 112 }, 113 }, 114 } 115 116 generalInfo = inspectimage.GeneralInfo{ 117 Name: "some-image-name", 118 RunImageMirrors: []config.RunImage{ 119 { 120 Image: "some-run-image", 121 Mirrors: []string{"first-mirror", "second-mirror"}, 122 }, 123 }, 124 } 125 }) 126 127 when("structured output", func() { 128 var ( 129 localBomDisplay []inspectimage.BOMEntryDisplay 130 remoteBomDisplay []inspectimage.BOMEntryDisplay 131 localBomWithExtensionDisplay []inspectimage.BOMEntryDisplay 132 remoteBomWithExtensionDisplay []inspectimage.BOMEntryDisplay 133 ) 134 it.Before(func() { 135 localBomDisplay = []inspectimage.BOMEntryDisplay{{ 136 Name: "local-require", 137 Version: "4.5.6", 138 Metadata: map[string]interface{}{ 139 "cool-local": "beans", 140 }, 141 Buildpack: dist.ModuleRef{ 142 ModuleInfo: dist.ModuleInfo{ 143 ID: "local-buildpack", 144 Version: "local-buildpack-version", 145 }, 146 }, 147 }} 148 remoteBomDisplay = []inspectimage.BOMEntryDisplay{{ 149 Name: "remote-require", 150 Version: "1.2.3", 151 Metadata: map[string]interface{}{ 152 "cool-remote": "beans", 153 }, 154 Buildpack: dist.ModuleRef{ 155 ModuleInfo: dist.ModuleInfo{ 156 ID: "remote-buildpack", 157 Version: "remote-buildpack-version", 158 }, 159 }, 160 }} 161 162 localBomWithExtensionDisplay = []inspectimage.BOMEntryDisplay{{ 163 Name: "local-require", 164 Version: "4.5.6", 165 Metadata: map[string]interface{}{ 166 "cool-local": "beans", 167 }, 168 Buildpack: dist.ModuleRef{ 169 ModuleInfo: dist.ModuleInfo{ 170 ID: "local-buildpack", 171 Version: "local-buildpack-version", 172 }, 173 }, 174 }} 175 remoteBomWithExtensionDisplay = []inspectimage.BOMEntryDisplay{{ 176 Name: "remote-require", 177 Version: "1.2.3", 178 Metadata: map[string]interface{}{ 179 "cool-remote": "beans", 180 }, 181 Buildpack: dist.ModuleRef{ 182 ModuleInfo: dist.ModuleInfo{ 183 ID: "remote-buildpack", 184 Version: "remote-buildpack-version", 185 }, 186 }, 187 }} 188 }) 189 it("passes correct info to structuredBOMWriter", func() { 190 var marshalInput interface{} 191 192 structuredBOMWriter := writer.StructuredBOMFormat{ 193 MarshalFunc: func(i interface{}) ([]byte, error) { 194 marshalInput = i 195 return []byte("marshalled"), nil 196 }, 197 } 198 199 err := structuredBOMWriter.Print(logger, generalInfo, localInfo, remoteInfo, nil, nil) 200 assert.Nil(err) 201 202 assert.Equal(marshalInput, inspectimage.BOMDisplay{ 203 Remote: remoteBomDisplay, 204 Local: localBomDisplay, 205 }) 206 }) 207 208 it("passes correct info to structuredBOMWriter", func() { 209 var marshalInput interface{} 210 211 structuredBOMWriter := writer.StructuredBOMFormat{ 212 MarshalFunc: func(i interface{}) ([]byte, error) { 213 marshalInput = i 214 return []byte("marshalled"), nil 215 }, 216 } 217 218 err := structuredBOMWriter.Print(logger, generalInfo, localWithExtensionInfo, remoteWithExtensionInfo, nil, nil) 219 assert.Nil(err) 220 221 assert.Equal(marshalInput, inspectimage.BOMDisplay{ 222 Remote: remoteBomWithExtensionDisplay, 223 Local: localBomWithExtensionDisplay, 224 }) 225 }) 226 when("a localErr is passed to Print", func() { 227 it("still marshals remote information", func() { 228 var marshalInput interface{} 229 230 localErr := errors.New("a local error occurred") 231 structuredBOMWriter := writer.StructuredBOMFormat{ 232 MarshalFunc: func(i interface{}) ([]byte, error) { 233 marshalInput = i 234 return []byte("marshalled"), nil 235 }, 236 } 237 238 err := structuredBOMWriter.Print(logger, generalInfo, nil, remoteInfo, localErr, nil) 239 assert.Nil(err) 240 241 assert.Equal(marshalInput, inspectimage.BOMDisplay{ 242 Remote: remoteBomDisplay, 243 Local: nil, 244 LocalErr: localErr.Error(), 245 }) 246 }) 247 }) 248 249 when("a localErr is passed to Print", func() { 250 it("still marshals remote information", func() { 251 var marshalInput interface{} 252 253 localErr := errors.New("a local error occurred") 254 structuredBOMWriter := writer.StructuredBOMFormat{ 255 MarshalFunc: func(i interface{}) ([]byte, error) { 256 marshalInput = i 257 return []byte("marshalled"), nil 258 }, 259 } 260 261 err := structuredBOMWriter.Print(logger, generalInfo, nil, remoteWithExtensionInfo, localErr, nil) 262 assert.Nil(err) 263 264 assert.Equal(marshalInput, inspectimage.BOMDisplay{ 265 Remote: remoteBomWithExtensionDisplay, 266 Local: nil, 267 LocalErr: localErr.Error(), 268 }) 269 }) 270 }) 271 272 when("a remoteErr is passed to Print", func() { 273 it("still marshals local information", func() { 274 var marshalInput interface{} 275 276 remoteErr := errors.New("a remote error occurred") 277 structuredBOMWriter := writer.StructuredBOMFormat{ 278 MarshalFunc: func(i interface{}) ([]byte, error) { 279 marshalInput = i 280 return []byte("marshalled"), nil 281 }, 282 } 283 284 err := structuredBOMWriter.Print(logger, generalInfo, localInfo, nil, nil, remoteErr) 285 assert.Nil(err) 286 287 assert.Equal(marshalInput, inspectimage.BOMDisplay{ 288 Remote: nil, 289 Local: localBomDisplay, 290 RemoteErr: remoteErr.Error(), 291 }) 292 }) 293 }) 294 295 when("a remoteErr is passed to Print", func() { 296 it("still marshals local information", func() { 297 var marshalInput interface{} 298 299 remoteErr := errors.New("a remote error occurred") 300 structuredBOMWriter := writer.StructuredBOMFormat{ 301 MarshalFunc: func(i interface{}) ([]byte, error) { 302 marshalInput = i 303 return []byte("marshalled"), nil 304 }, 305 } 306 307 err := structuredBOMWriter.Print(logger, generalInfo, localWithExtensionInfo, nil, nil, remoteErr) 308 assert.Nil(err) 309 310 assert.Equal(marshalInput, inspectimage.BOMDisplay{ 311 Remote: nil, 312 Local: localBomWithExtensionDisplay, 313 RemoteErr: remoteErr.Error(), 314 }) 315 }) 316 }) 317 }) 318 319 // Just test error cases, all error-free cases will be tested in JSON, TOML, and YAML subclasses. 320 when("failure cases", func() { 321 when("both info objects are nil", func() { 322 it("displays a 'missing image' error message'", func() { 323 structuredBOMWriter := writer.StructuredBOMFormat{ 324 MarshalFunc: testMarshalFunc, 325 } 326 327 err := structuredBOMWriter.Print(logger, generalInfo, nil, nil, nil, nil) 328 assert.ErrorWithMessage(err, fmt.Sprintf("unable to find image '%s' locally or remotely", "some-image-name")) 329 }) 330 }) 331 when("fetching local and remote info errors", func() { 332 it("returns an error", func() { 333 structuredBOMWriter := writer.StructuredBOMFormat{ 334 MarshalFunc: func(i interface{}) ([]byte, error) { 335 return []byte("cool"), nil 336 }, 337 } 338 remoteErr := errors.New("a remote error occurred") 339 localErr := errors.New("a local error occurred") 340 341 err := structuredBOMWriter.Print(logger, generalInfo, localInfo, remoteInfo, localErr, remoteErr) 342 assert.ErrorContains(err, remoteErr.Error()) 343 assert.ErrorContains(err, localErr.Error()) 344 }) 345 }) 346 347 when("fetching local and remote info errors", func() { 348 it("returns an error", func() { 349 structuredBOMWriter := writer.StructuredBOMFormat{ 350 MarshalFunc: func(i interface{}) ([]byte, error) { 351 return []byte("cool"), nil 352 }, 353 } 354 remoteErr := errors.New("a remote error occurred") 355 localErr := errors.New("a local error occurred") 356 357 err := structuredBOMWriter.Print(logger, generalInfo, localWithExtensionInfo, remoteWithExtensionInfo, localErr, remoteErr) 358 assert.ErrorContains(err, remoteErr.Error()) 359 assert.ErrorContains(err, localErr.Error()) 360 }) 361 }) 362 }) 363 }) 364 }