github.com/wolfi-dev/wolfictl@v0.16.11/pkg/configs/advisory/v2/document_test.go (about)

     1  package v2
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/chainguard-dev/yam/pkg/yam/formatted"
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/hashicorp/go-version"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  	"gopkg.in/yaml.v3"
    17  )
    18  
    19  func TestDocument_Validate(t *testing.T) {
    20  	testTime := Timestamp(time.Date(2022, 9, 26, 0, 0, 0, 0, time.UTC))
    21  	testValidAdvisory := Advisory{
    22  		ID: "CVE-2020-0001",
    23  		Events: []Event{
    24  			{
    25  				Timestamp: testTime,
    26  				Type:      EventTypeDetection,
    27  				Data:      Detection{Type: DetectionTypeManual},
    28  			},
    29  		},
    30  	}
    31  
    32  	tests := []struct {
    33  		name    string
    34  		doc     Document
    35  		wantErr bool
    36  	}{
    37  		{
    38  			name: "valid",
    39  			doc: Document{
    40  				SchemaVersion: SchemaVersion,
    41  				Package: Package{
    42  					Name: "good-package",
    43  				},
    44  				Advisories: Advisories{testValidAdvisory},
    45  			},
    46  			wantErr: false,
    47  		},
    48  		{
    49  			name: "schema is newer",
    50  			doc: Document{
    51  				SchemaVersion: newerSchemaVersion(SchemaVersion),
    52  				Package: Package{
    53  					Name: "good-package",
    54  				},
    55  				Advisories: Advisories{testValidAdvisory},
    56  			},
    57  			wantErr: true,
    58  		},
    59  		{
    60  			name: "schema too old",
    61  			doc: Document{
    62  				SchemaVersion: "1.0.0",
    63  				Package: Package{
    64  					Name: "good-package",
    65  				},
    66  				Advisories: Advisories{testValidAdvisory},
    67  			},
    68  			wantErr: true,
    69  		},
    70  		{
    71  			name: "missing package name",
    72  			doc: Document{
    73  				SchemaVersion: SchemaVersion,
    74  				Package:       Package{},
    75  				Advisories:    Advisories{testValidAdvisory},
    76  			},
    77  			wantErr: true,
    78  		},
    79  		{
    80  			name: "no advisories",
    81  			doc: Document{
    82  				SchemaVersion: SchemaVersion,
    83  				Package: Package{
    84  					Name: "good-package",
    85  				},
    86  			},
    87  			wantErr: true,
    88  		},
    89  	}
    90  
    91  	for _, tt := range tests {
    92  		t.Run(tt.name, func(t *testing.T) {
    93  			err := tt.doc.Validate()
    94  			if tt.wantErr && err == nil {
    95  				t.Errorf("expected error, got nil")
    96  			}
    97  			if !tt.wantErr && err != nil {
    98  				t.Errorf("unexpected error: %v", err)
    99  			}
   100  		})
   101  	}
   102  }
   103  
   104  func newerSchemaVersion(currentSchemaVersion string) string {
   105  	v, _ := version.NewVersion(currentSchemaVersion) //nolint:errcheck
   106  
   107  	segments := v.Segments()
   108  	if len(segments) <= 1 {
   109  		return fmt.Sprintf("%s.1", currentSchemaVersion)
   110  	}
   111  
   112  	return fmt.Sprintf("%d.%d", segments[0], segments[1]+1)
   113  }
   114  
   115  func TestDocument_full_coverage(t *testing.T) {
   116  	testTime := Timestamp(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
   117  
   118  	testDocument := Document{
   119  		SchemaVersion: SchemaVersion,
   120  		Package: Package{
   121  			Name: "full",
   122  		},
   123  		Advisories: Advisories{
   124  			{
   125  				ID: "CVE-2000-0001",
   126  				Aliases: []string{
   127  					"GHSA-xxxx-xxxx-xxx9",
   128  					"GO-2000-0001",
   129  				},
   130  				Events: []Event{
   131  					{
   132  						Timestamp: testTime,
   133  						Type:      EventTypeDetection,
   134  						Data: Detection{
   135  							Type: DetectionTypeManual,
   136  						},
   137  					},
   138  					{
   139  						Timestamp: testTime,
   140  						Type:      EventTypeDetection,
   141  						Data: Detection{
   142  							Type: DetectionTypeNVDAPI,
   143  							Data: DetectionNVDAPI{
   144  								CPESearched: "cpe:2.3:a:*:tinyxml:*:*:*:*:*:*:*:*",
   145  								CPEFound:    "cpe:2.3:a:tinyxml_project:tinyxml:*:*:*:*:*:*:*:*",
   146  							},
   147  						},
   148  					},
   149  					{
   150  						Timestamp: testTime,
   151  						Type:      EventTypeDetection,
   152  						Data: Detection{
   153  							Type: DetectionTypeScanV1,
   154  							Data: DetectionScanV1{
   155  								SubpackageName:    "test-sub",
   156  								ComponentID:       "fe8053a3adedc5d0",
   157  								ComponentName:     "github.com/docker/distribution",
   158  								ComponentVersion:  "v2.8.1+incompatible",
   159  								ComponentType:     "go-module",
   160  								ComponentLocation: "/usr/bin/crane",
   161  								Scanner:           "grype",
   162  							},
   163  						},
   164  					},
   165  					{
   166  						Timestamp: testTime,
   167  						Type:      EventTypeTruePositiveDetermination,
   168  						Data: TruePositiveDetermination{
   169  							Note: "Something something true positive.",
   170  						},
   171  					},
   172  					{
   173  						Timestamp: testTime,
   174  						Type:      EventTypeFalsePositiveDetermination,
   175  						Data: FalsePositiveDetermination{
   176  							Type: FPTypeVulnerabilityRecordAnalysisContested,
   177  							Note: "Something something false positive.",
   178  						},
   179  					},
   180  					{
   181  						Timestamp: testTime,
   182  						Type:      EventTypeFalsePositiveDetermination,
   183  						Data: FalsePositiveDetermination{
   184  							Type: FPTypeComponentVulnerabilityMismatch,
   185  							Note: "Something something false positive.",
   186  						},
   187  					},
   188  					{
   189  						Timestamp: testTime,
   190  						Type:      EventTypeFalsePositiveDetermination,
   191  						Data: FalsePositiveDetermination{
   192  							Type: FPTypeVulnerableCodeVersionNotUsed,
   193  							Note: "Something something false positive.",
   194  						},
   195  					},
   196  					{
   197  						Timestamp: testTime,
   198  						Type:      EventTypeFalsePositiveDetermination,
   199  						Data: FalsePositiveDetermination{
   200  							Type: FPTypeVulnerableCodeNotIncludedInPackage,
   201  							Note: "Something something false positive.",
   202  						},
   203  					},
   204  					{
   205  						Timestamp: testTime,
   206  						Type:      EventTypeFalsePositiveDetermination,
   207  						Data: FalsePositiveDetermination{
   208  							Type: FPTypeVulnerableCodeNotInExecutionPath,
   209  							Note: "Something something false positive.",
   210  						},
   211  					},
   212  					{
   213  						Timestamp: testTime,
   214  						Type:      EventTypeFalsePositiveDetermination,
   215  						Data: FalsePositiveDetermination{
   216  							Type: FPTypeVulnerableCodeCannotBeControlledByAdversary,
   217  							Note: "Something something false positive.",
   218  						},
   219  					},
   220  					{
   221  						Timestamp: testTime,
   222  						Type:      EventTypeFalsePositiveDetermination,
   223  						Data: FalsePositiveDetermination{
   224  							Type: FPTypeInlineMitigationsExist,
   225  							Note: "Something something false positive.",
   226  						},
   227  					},
   228  					{
   229  						Timestamp: testTime,
   230  						Type:      EventTypeFixed,
   231  						Data: Fixed{
   232  							FixedVersion: "1.2.3-r4",
   233  						},
   234  					},
   235  					{
   236  						Timestamp: testTime,
   237  						Type:      EventTypeAnalysisNotPlanned,
   238  						Data: AnalysisNotPlanned{
   239  							Note: "Something something analysis not planned.",
   240  						},
   241  					},
   242  					{
   243  						Timestamp: testTime,
   244  						Type:      EventTypeFixNotPlanned,
   245  						Data: FixNotPlanned{
   246  							Note: "Something something fix not planned.",
   247  						},
   248  					},
   249  					{
   250  						Timestamp: testTime,
   251  						Type:      EventTypePendingUpstreamFix,
   252  						Data: PendingUpstreamFix{
   253  							Note: "Something something pending upstream fix.",
   254  						},
   255  					},
   256  				},
   257  			},
   258  		},
   259  	}
   260  
   261  	f, err := os.Open("testdata/full.advisories.yaml") // Note: Keep this document using the latest schema.
   262  	require.NoError(t, err)
   263  
   264  	t.Run("decode", func(t *testing.T) {
   265  		expected := testDocument
   266  
   267  		actual, err := decodeDocument(f)
   268  		require.NoError(t, err)
   269  
   270  		if diff := cmp.Diff(expected, *actual); diff != "" {
   271  			t.Errorf("unexpected document (-want +got):\n%s", diff)
   272  			t.FailNow()
   273  		}
   274  
   275  		t.Run("document should be valid", func(t *testing.T) {
   276  			err := actual.Validate()
   277  			assert.NoError(t, err)
   278  		})
   279  	})
   280  
   281  	// Reset seek position to prepare for reading in next test.
   282  	_, err = f.Seek(0, io.SeekStart)
   283  	require.NoError(t, err)
   284  
   285  	t.Run("encode", func(t *testing.T) {
   286  		actual := new(bytes.Buffer)
   287  
   288  		encoder, err := formatted.NewEncoder(actual).UseOptions(formatted.EncodeOptions{
   289  			Indent:         2,
   290  			GapExpressions: []string{"."},
   291  		})
   292  		require.NoError(t, err)
   293  
   294  		node := &yaml.Node{} // TODO: this can be simplified when yam supports encoding an empty interface
   295  		err = node.Encode(testDocument)
   296  		require.NoError(t, err)
   297  		err = encoder.Encode(node)
   298  		require.NoError(t, err)
   299  
   300  		expectedBytes, err := io.ReadAll(f)
   301  		require.NoError(t, err)
   302  
   303  		if diff := cmp.Diff(string(expectedBytes), actual.String()); diff != "" {
   304  			t.Errorf("unexpected document (-want +got):\n%s", diff)
   305  		}
   306  	})
   307  }
   308  
   309  func TestDocument_DecodeFutureNonBreakingSchema(t *testing.T) {
   310  	f, err := os.Open("testdata/future.advisories.yaml")
   311  	require.NoError(t, err)
   312  
   313  	var doc Document
   314  	err = yaml.NewDecoder(f).Decode(&doc)
   315  	assert.NoError(t, err)
   316  }