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 }