github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/cmd/syft/cli/options/writer_test.go (about)

     1  package options
     2  
     3  import (
     4  	"io"
     5  	"path/filepath"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/docker/docker/pkg/homedir"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/anchore/syft/syft/sbom"
    14  )
    15  
    16  func Test_MakeSBOMWriter(t *testing.T) {
    17  	tests := []struct {
    18  		name    string
    19  		outputs []string
    20  		wantErr assert.ErrorAssertionFunc
    21  	}{
    22  		{
    23  			name:    "go case",
    24  			outputs: []string{"json"},
    25  			wantErr: assert.NoError,
    26  		},
    27  		{
    28  			name:    "multiple",
    29  			outputs: []string{"table", "json"},
    30  			wantErr: assert.NoError,
    31  		},
    32  		{
    33  			name:    "unknown format",
    34  			outputs: []string{"unknown"},
    35  			wantErr: func(t assert.TestingT, err error, bla ...interface{}) bool {
    36  				return assert.ErrorContains(t, err, `unsupported output format "unknown", supported formats are:`)
    37  			},
    38  		},
    39  	}
    40  
    41  	for _, tt := range tests {
    42  		t.Run(tt.name, func(t *testing.T) {
    43  			opt := DefaultOutput()
    44  			require.NoError(t, opt.Format.PostLoad())
    45  			encoders, err := opt.Encoders()
    46  			require.NoError(t, err)
    47  			_, err = makeSBOMWriter(tt.outputs, "", encoders)
    48  			tt.wantErr(t, err)
    49  		})
    50  	}
    51  }
    52  
    53  func dummyFormat(name string) sbom.FormatEncoder {
    54  	return dummyEncoder{name: name}
    55  }
    56  
    57  var _ sbom.FormatEncoder = (*dummyEncoder)(nil)
    58  
    59  type dummyEncoder struct {
    60  	name string
    61  }
    62  
    63  func (d dummyEncoder) ID() sbom.FormatID {
    64  	return sbom.FormatID(d.name)
    65  }
    66  
    67  func (d dummyEncoder) Aliases() []string {
    68  	return nil
    69  }
    70  
    71  func (d dummyEncoder) Version() string {
    72  	return sbom.AnyVersion
    73  }
    74  
    75  func (d dummyEncoder) Encode(writer io.Writer, s sbom.SBOM) error {
    76  	return nil
    77  }
    78  
    79  func Test_newSBOMMultiWriter(t *testing.T) {
    80  	type writerConfig struct {
    81  		format string
    82  		file   string
    83  	}
    84  
    85  	tmp := t.TempDir()
    86  
    87  	testName := func(options []sbomWriterDescription, err bool) string {
    88  		var out []string
    89  		for _, opt := range options {
    90  			out = append(out, string(opt.Format.ID())+"="+opt.Path)
    91  		}
    92  		errs := ""
    93  		if err {
    94  			errs = "(err)"
    95  		}
    96  		return strings.Join(out, ", ") + errs
    97  	}
    98  
    99  	tests := []struct {
   100  		outputs  []sbomWriterDescription
   101  		err      bool
   102  		expected []writerConfig
   103  	}{
   104  		{
   105  			outputs: []sbomWriterDescription{},
   106  			err:     true,
   107  		},
   108  		{
   109  			outputs: []sbomWriterDescription{
   110  				{
   111  					Format: dummyFormat("table"),
   112  					Path:   "",
   113  				},
   114  			},
   115  			expected: []writerConfig{
   116  				{
   117  					format: "table",
   118  				},
   119  			},
   120  		},
   121  		{
   122  			outputs: []sbomWriterDescription{
   123  				{
   124  					Format: dummyFormat("json"),
   125  				},
   126  			},
   127  			expected: []writerConfig{
   128  				{
   129  					format: "json",
   130  				},
   131  			},
   132  		},
   133  		{
   134  			outputs: []sbomWriterDescription{
   135  				{
   136  					Format: dummyFormat("json"),
   137  					Path:   "test-2.json",
   138  				},
   139  			},
   140  			expected: []writerConfig{
   141  				{
   142  					format: "json",
   143  					file:   "test-2.json",
   144  				},
   145  			},
   146  		},
   147  		{
   148  			outputs: []sbomWriterDescription{
   149  				{
   150  					Format: dummyFormat("json"),
   151  					Path:   "test-3/1.json",
   152  				},
   153  				{
   154  					Format: dummyFormat("spdx-json"),
   155  					Path:   "test-3/2.json",
   156  				},
   157  			},
   158  			expected: []writerConfig{
   159  				{
   160  					format: "json",
   161  					file:   "test-3/1.json",
   162  				},
   163  				{
   164  					format: "spdx-json",
   165  					file:   "test-3/2.json",
   166  				},
   167  			},
   168  		},
   169  		{
   170  			outputs: []sbomWriterDescription{
   171  				{
   172  					Format: dummyFormat("text"),
   173  				},
   174  				{
   175  					Format: dummyFormat("spdx-json"),
   176  					Path:   "test-4.json",
   177  				},
   178  			},
   179  			expected: []writerConfig{
   180  				{
   181  					format: "text",
   182  				},
   183  				{
   184  					format: "spdx-json",
   185  					file:   "test-4.json",
   186  				},
   187  			},
   188  		},
   189  	}
   190  
   191  	for _, test := range tests {
   192  		t.Run(testName(test.outputs, test.err), func(t *testing.T) {
   193  			outputs := test.outputs
   194  			for i := range outputs {
   195  				if outputs[i].Path != "" {
   196  					outputs[i].Path = tmp + outputs[i].Path
   197  				}
   198  			}
   199  
   200  			mw, err := newSBOMMultiWriter(outputs...)
   201  
   202  			if test.err {
   203  				assert.Error(t, err)
   204  				return
   205  			} else {
   206  				assert.NoError(t, err)
   207  			}
   208  
   209  			assert.Len(t, mw.writers, len(test.expected))
   210  
   211  			for i, e := range test.expected {
   212  				switch w := mw.writers[i].(type) {
   213  				case *sbomStreamWriter:
   214  					assert.Equal(t, string(w.format.ID()), e.format)
   215  					assert.NotNil(t, w.out)
   216  					if e.file != "" {
   217  						assert.FileExists(t, tmp+e.file)
   218  					}
   219  				case *sbomPublisher:
   220  					assert.Equal(t, string(w.format.ID()), e.format)
   221  				default:
   222  					t.Fatalf("unknown writer type: %T", w)
   223  				}
   224  
   225  			}
   226  		})
   227  	}
   228  }
   229  
   230  func Test_newSBOMWriterDescription(t *testing.T) {
   231  	tests := []struct {
   232  		name     string
   233  		path     string
   234  		expected string
   235  	}{
   236  		{
   237  			name:     "expand home dir",
   238  			path:     "~/place.txt",
   239  			expected: filepath.Join(homedir.Get(), "place.txt"),
   240  		},
   241  		{
   242  			name:     "passthrough other paths",
   243  			path:     "/other/place.txt",
   244  			expected: "/other/place.txt",
   245  		},
   246  		{
   247  			name:     "no path",
   248  			path:     "",
   249  			expected: "",
   250  		},
   251  	}
   252  	for _, tt := range tests {
   253  		t.Run(tt.name, func(t *testing.T) {
   254  			o := newSBOMWriterDescription(dummyFormat("table"), tt.path)
   255  			assert.Equal(t, tt.expected, o.Path)
   256  		})
   257  	}
   258  }
   259  
   260  func Test_formatVersionOptions(t *testing.T) {
   261  
   262  	tests := []struct {
   263  		name             string
   264  		nameVersionPairs []string
   265  		want             string
   266  	}{
   267  		{
   268  			name: "gocase",
   269  			nameVersionPairs: []string{
   270  				"cyclonedx-json@1.2", "cyclonedx-json@1.3", "cyclonedx-json@1.4", "cyclonedx-json@1.5",
   271  				"cyclonedx-xml@1.0", "cyclonedx-xml@1.1", "cyclonedx-xml@1.2", "cyclonedx-xml@1.3",
   272  				"cyclonedx-xml@1.4", "cyclonedx-xml@1.5", "github-json", "spdx-json@2.2", "spdx-json@2.3",
   273  				"spdx-tag-value@2.1", "spdx-tag-value@2.2", "spdx-tag-value@2.3", "syft-json@11.0.0",
   274  				"syft-table", "syft-text", "template",
   275  			},
   276  			want: `
   277  Available formats:
   278     - cyclonedx-json @ 1.2, 1.3, 1.4, 1.5
   279     - cyclonedx-xml @ 1.0, 1.1, 1.2, 1.3, 1.4, 1.5
   280     - github-json
   281     - spdx-json @ 2.2, 2.3
   282     - spdx-tag-value @ 2.1, 2.2, 2.3
   283     - syft-json
   284     - syft-table
   285     - syft-text
   286     - template`,
   287  		},
   288  	}
   289  	for _, tt := range tests {
   290  		t.Run(tt.name, func(t *testing.T) {
   291  			assert.Equal(t, tt.want, formatVersionOptions(tt.nameVersionPairs))
   292  		})
   293  	}
   294  }