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  }