github.com/YousefHaggyHeroku/pack@v1.5.5/internal/builder/writer/json_test.go (about) 1 package writer_test 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "testing" 9 10 "github.com/Masterminds/semver" 11 "github.com/heroku/color" 12 "github.com/sclevine/spec" 13 "github.com/sclevine/spec/report" 14 15 "github.com/buildpacks/lifecycle/api" 16 17 "github.com/YousefHaggyHeroku/pack" 18 pubbldr "github.com/YousefHaggyHeroku/pack/builder" 19 "github.com/YousefHaggyHeroku/pack/internal/builder" 20 "github.com/YousefHaggyHeroku/pack/internal/builder/writer" 21 "github.com/YousefHaggyHeroku/pack/internal/config" 22 "github.com/YousefHaggyHeroku/pack/internal/dist" 23 ilogging "github.com/YousefHaggyHeroku/pack/internal/logging" 24 h "github.com/YousefHaggyHeroku/pack/testhelpers" 25 ) 26 27 func TestJSON(t *testing.T) { 28 color.Disable(true) 29 defer color.Disable(false) 30 spec.Run(t, "Builder Writer", testJSON, spec.Parallel(), spec.Report(report.Terminal{})) 31 } 32 33 func testJSON(t *testing.T, when spec.G, it spec.S) { 34 const ( 35 expectedRemoteRunImages = `"run_images": [ 36 { 37 "name": "first/local", 38 "user_configured": true 39 }, 40 { 41 "name": "second/local", 42 "user_configured": true 43 }, 44 { 45 "name": "some/run-image" 46 }, 47 { 48 "name": "first/default" 49 }, 50 { 51 "name": "second/default" 52 } 53 ]` 54 expectedLocalRunImages = `"run_images": [ 55 { 56 "name": "first/local", 57 "user_configured": true 58 }, 59 { 60 "name": "second/local", 61 "user_configured": true 62 }, 63 { 64 "name": "some/run-image" 65 }, 66 { 67 "name": "first/local-default" 68 }, 69 { 70 "name": "second/local-default" 71 } 72 ]` 73 74 expectedBuildpacks = `"buildpacks": [ 75 { 76 "id": "test.top.nested", 77 "version": "test.top.nested.version" 78 }, 79 { 80 "id": "test.nested", 81 "homepage": "http://geocities.com/top-bp" 82 }, 83 { 84 "id": "test.bp.one", 85 "version": "test.bp.one.version", 86 "homepage": "http://geocities.com/cool-bp" 87 }, 88 { 89 "id": "test.bp.two", 90 "version": "test.bp.two.version" 91 }, 92 { 93 "id": "test.bp.three", 94 "version": "test.bp.three.version" 95 } 96 ]` 97 expectedDetectionOrder = `"detection_order": [ 98 { 99 "buildpacks": [ 100 { 101 "id": "test.top.nested", 102 "version": "test.top.nested.version", 103 "buildpacks": [ 104 { 105 "id": "test.nested", 106 "homepage": "http://geocities.com/top-bp", 107 "buildpacks": [ 108 { 109 "id": "test.bp.one", 110 "version": "test.bp.one.version", 111 "homepage": "http://geocities.com/cool-bp", 112 "optional": true 113 } 114 ] 115 }, 116 { 117 "id": "test.bp.three", 118 "version": "test.bp.three.version", 119 "optional": true 120 }, 121 { 122 "id": "test.nested.two", 123 "version": "test.nested.two.version", 124 "buildpacks": [ 125 { 126 "id": "test.bp.one", 127 "version": "test.bp.one.version", 128 "homepage": "http://geocities.com/cool-bp", 129 "optional": true, 130 "cyclic": true 131 } 132 ] 133 } 134 ] 135 }, 136 { 137 "id": "test.bp.two", 138 "version": "test.bp.two.version", 139 "optional": true 140 } 141 ] 142 }, 143 { 144 "id": "test.bp.three", 145 "version": "test.bp.three.version" 146 } 147 ]` 148 expectedStackWithMixins = `"stack": { 149 "id": "test.stack.id", 150 "mixins": [ 151 "mixin1", 152 "mixin2", 153 "build:mixin3", 154 "build:mixin4" 155 ] 156 }` 157 ) 158 159 var ( 160 assert = h.NewAssertionManager(t) 161 outBuf bytes.Buffer 162 163 remoteInfo *pack.BuilderInfo 164 localInfo *pack.BuilderInfo 165 166 expectedRemoteInfo = fmt.Sprintf(`"remote_info": { 167 "description": "Some remote description", 168 "created_by": { 169 "name": "Pack CLI", 170 "version": "1.2.3" 171 }, 172 "stack": { 173 "id": "test.stack.id" 174 }, 175 "lifecycle": { 176 "version": "6.7.8", 177 "buildpack_apis": { 178 "deprecated": null, 179 "supported": [ 180 "1.2", 181 "2.3" 182 ] 183 }, 184 "platform_apis": { 185 "deprecated": [ 186 "0.1", 187 "1.2" 188 ], 189 "supported": [ 190 "4.5" 191 ] 192 } 193 }, 194 %s, 195 %s, 196 %s 197 }`, expectedRemoteRunImages, expectedBuildpacks, expectedDetectionOrder) 198 199 expectedLocalInfo = fmt.Sprintf(`"local_info": { 200 "description": "Some local description", 201 "created_by": { 202 "name": "Pack CLI", 203 "version": "4.5.6" 204 }, 205 "stack": { 206 "id": "test.stack.id" 207 }, 208 "lifecycle": { 209 "version": "4.5.6", 210 "buildpack_apis": { 211 "deprecated": [ 212 "4.5", 213 "6.7" 214 ], 215 "supported": [ 216 "8.9", 217 "10.11" 218 ] 219 }, 220 "platform_apis": { 221 "deprecated": null, 222 "supported": [ 223 "7.8" 224 ] 225 } 226 }, 227 %s, 228 %s, 229 %s 230 }`, expectedLocalRunImages, expectedBuildpacks, expectedDetectionOrder) 231 232 expectedPrettifiedJSON = fmt.Sprintf(`{ 233 "builder_name": "test-builder", 234 "trusted": false, 235 "default": false, 236 %s, 237 %s 238 } 239 `, expectedRemoteInfo, expectedLocalInfo) 240 ) 241 242 when("Print", func() { 243 it.Before(func() { 244 remoteInfo = &pack.BuilderInfo{ 245 Description: "Some remote description", 246 Stack: "test.stack.id", 247 Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, 248 RunImage: "some/run-image", 249 RunImageMirrors: []string{"first/default", "second/default"}, 250 Buildpacks: buildpacks, 251 Order: order, 252 BuildpackLayers: dist.BuildpackLayers{}, 253 Lifecycle: builder.LifecycleDescriptor{ 254 Info: builder.LifecycleInfo{ 255 Version: &builder.Version{ 256 Version: *semver.MustParse("6.7.8"), 257 }, 258 }, 259 APIs: builder.LifecycleAPIs{ 260 Buildpack: builder.APIVersions{ 261 Deprecated: nil, 262 Supported: builder.APISet{api.MustParse("1.2"), api.MustParse("2.3")}, 263 }, 264 Platform: builder.APIVersions{ 265 Deprecated: builder.APISet{api.MustParse("0.1"), api.MustParse("1.2")}, 266 Supported: builder.APISet{api.MustParse("4.5")}, 267 }, 268 }, 269 }, 270 CreatedBy: builder.CreatorMetadata{ 271 Name: "Pack CLI", 272 Version: "1.2.3", 273 }, 274 } 275 276 localInfo = &pack.BuilderInfo{ 277 Description: "Some local description", 278 Stack: "test.stack.id", 279 Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, 280 RunImage: "some/run-image", 281 RunImageMirrors: []string{"first/local-default", "second/local-default"}, 282 Buildpacks: buildpacks, 283 Order: order, 284 BuildpackLayers: dist.BuildpackLayers{}, 285 Lifecycle: builder.LifecycleDescriptor{ 286 Info: builder.LifecycleInfo{ 287 Version: &builder.Version{ 288 Version: *semver.MustParse("4.5.6"), 289 }, 290 }, 291 APIs: builder.LifecycleAPIs{ 292 Buildpack: builder.APIVersions{ 293 Deprecated: builder.APISet{api.MustParse("4.5"), api.MustParse("6.7")}, 294 Supported: builder.APISet{api.MustParse("8.9"), api.MustParse("10.11")}, 295 }, 296 Platform: builder.APIVersions{ 297 Deprecated: nil, 298 Supported: builder.APISet{api.MustParse("7.8")}, 299 }, 300 }, 301 }, 302 CreatedBy: builder.CreatorMetadata{ 303 Name: "Pack CLI", 304 Version: "4.5.6", 305 }, 306 } 307 }) 308 309 it("prints both local remote builders as valid JSON", func() { 310 jsonWriter := writer.NewJSON() 311 312 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf) 313 err := jsonWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 314 assert.Nil(err) 315 316 prettyJSON, err := validPrettifiedJSONOutput(outBuf) 317 assert.Nil(err) 318 319 assert.ContainsJSON(prettyJSON, expectedPrettifiedJSON) 320 }) 321 322 when("builder doesn't exist locally or remotely", func() { 323 it("returns an error", func() { 324 jsonWriter := writer.NewJSON() 325 326 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf) 327 err := jsonWriter.Print(logger, localRunImages, nil, nil, nil, nil, sharedBuilderInfo) 328 assert.ErrorWithMessage(err, "unable to find builder 'test-builder' locally or remotely") 329 }) 330 }) 331 332 when("builder doesn't exist locally", func() { 333 it("shows null for local builder, and normal output for remote", func() { 334 jsonWriter := writer.NewJSON() 335 336 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf) 337 err := jsonWriter.Print(logger, localRunImages, nil, remoteInfo, nil, nil, sharedBuilderInfo) 338 assert.Nil(err) 339 340 prettyJSON, err := validPrettifiedJSONOutput(outBuf) 341 assert.Nil(err) 342 343 assert.ContainsJSON(prettyJSON, `{"local_info": null}`) 344 assert.ContainsJSON(prettyJSON, fmt.Sprintf("{%s}", expectedRemoteInfo)) 345 }) 346 }) 347 348 when("builder doesn't exist remotely", func() { 349 it("shows null for remote builder, and normal output for local", func() { 350 jsonWriter := writer.NewJSON() 351 352 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf) 353 err := jsonWriter.Print(logger, localRunImages, localInfo, nil, nil, nil, sharedBuilderInfo) 354 assert.Nil(err) 355 356 prettyJSON, err := validPrettifiedJSONOutput(outBuf) 357 assert.Nil(err) 358 359 assert.ContainsJSON(prettyJSON, `{"remote_info": null}`) 360 assert.ContainsJSON(prettyJSON, fmt.Sprintf("{%s}", expectedLocalInfo)) 361 }) 362 }) 363 364 when("localErr is an error", func() { 365 it("returns the error, and doesn't write any json output", func() { 366 expectedErr := errors.New("failed to retrieve local info") 367 368 jsonWriter := writer.NewJSON() 369 370 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf) 371 err := jsonWriter.Print(logger, localRunImages, localInfo, remoteInfo, expectedErr, nil, sharedBuilderInfo) 372 assert.ErrorWithMessage(err, "preparing output for 'test-builder': failed to retrieve local info") 373 374 assert.Equal(outBuf.String(), "") 375 }) 376 }) 377 378 when("remoteErr is an error", func() { 379 it("returns the error, and doesn't write any json output", func() { 380 expectedErr := errors.New("failed to retrieve remote info") 381 382 jsonWriter := writer.NewJSON() 383 384 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf) 385 err := jsonWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, expectedErr, sharedBuilderInfo) 386 assert.ErrorWithMessage(err, "preparing output for 'test-builder': failed to retrieve remote info") 387 388 assert.Equal(outBuf.String(), "") 389 }) 390 }) 391 392 when("logger is verbose", func() { 393 it("displays mixins associated with the stack", func() { 394 jsonWriter := writer.NewJSON() 395 396 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf, ilogging.WithVerbose()) 397 err := jsonWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 398 assert.Nil(err) 399 400 prettifiedJSON, err := validPrettifiedJSONOutput(outBuf) 401 assert.Nil(err) 402 403 assert.ContainsJSON(prettifiedJSON, fmt.Sprintf("{%s}", expectedStackWithMixins)) 404 }) 405 }) 406 407 when("no run images are specified", func() { 408 it("displays run images as empty list", func() { 409 localInfo.RunImage = "" 410 localInfo.RunImageMirrors = []string{} 411 remoteInfo.RunImage = "" 412 remoteInfo.RunImageMirrors = []string{} 413 emptyLocalRunImages := []config.RunImage{} 414 415 jsonWriter := writer.NewJSON() 416 417 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf, ilogging.WithVerbose()) 418 err := jsonWriter.Print(logger, emptyLocalRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 419 assert.Nil(err) 420 421 prettifiedJSON, err := validPrettifiedJSONOutput(outBuf) 422 assert.Nil(err) 423 424 assert.ContainsJSON(prettifiedJSON, `{"run_images": []}`) 425 }) 426 }) 427 428 when("no buildpacks are specified", func() { 429 it("displays buildpacks as empty list", func() { 430 localInfo.Buildpacks = []dist.BuildpackInfo{} 431 remoteInfo.Buildpacks = []dist.BuildpackInfo{} 432 433 jsonWriter := writer.NewJSON() 434 435 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf, ilogging.WithVerbose()) 436 err := jsonWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 437 assert.Nil(err) 438 439 prettifiedJSON, err := validPrettifiedJSONOutput(outBuf) 440 assert.Nil(err) 441 442 assert.ContainsJSON(prettifiedJSON, `{"buildpacks": []}`) 443 }) 444 }) 445 446 when("no detection order is specified", func() { 447 it("displays detection order as empty list", func() { 448 localInfo.Order = pubbldr.DetectionOrder{} 449 remoteInfo.Order = pubbldr.DetectionOrder{} 450 451 jsonWriter := writer.NewJSON() 452 453 logger := ilogging.NewLogWithWriters(&outBuf, &outBuf, ilogging.WithVerbose()) 454 err := jsonWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 455 assert.Nil(err) 456 457 prettifiedJSON, err := validPrettifiedJSONOutput(outBuf) 458 assert.Nil(err) 459 460 assert.ContainsJSON(prettifiedJSON, `{"detection_order": []}`) 461 }) 462 }) 463 }) 464 } 465 466 func validPrettifiedJSONOutput(source bytes.Buffer) (string, error) { 467 err := json.Unmarshal(source.Bytes(), &struct{}{}) 468 if err != nil { 469 return "", fmt.Errorf("failed to unmarshal to json: %w", err) 470 } 471 472 var prettifiedOutput bytes.Buffer 473 err = json.Indent(&prettifiedOutput, source.Bytes(), "", " ") 474 if err != nil { 475 return "", fmt.Errorf("failed to prettify source json: %w", err) 476 } 477 478 return prettifiedOutput.String(), nil 479 }