github.com/google/osv-scalibr@v0.4.1/annotator/annotator_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package annotator_test 16 17 import ( 18 "context" 19 "errors" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/google/go-cmp/cmp/cmpopts" 24 "github.com/google/go-cpy/cpy" 25 "github.com/google/osv-scalibr/annotator" 26 "github.com/google/osv-scalibr/annotator/cachedir" 27 "github.com/google/osv-scalibr/extractor" 28 "github.com/google/osv-scalibr/inventory" 29 "github.com/google/osv-scalibr/inventory/vex" 30 "github.com/google/osv-scalibr/plugin" 31 "google.golang.org/protobuf/proto" 32 ) 33 34 type succeedingAnnotator struct{} 35 36 func (succeedingAnnotator) Name() string { return "succeeding-annotator" } 37 func (succeedingAnnotator) Version() int { return 1 } 38 func (succeedingAnnotator) Requirements() *plugin.Capabilities { return &plugin.Capabilities{} } 39 func (succeedingAnnotator) Annotate(ctx context.Context, input *annotator.ScanInput, results *inventory.Inventory) error { 40 return nil 41 } 42 43 type failingAnnotator struct{} 44 45 func (failingAnnotator) Name() string { return "failing-annotator" } 46 func (failingAnnotator) Version() int { return 2 } 47 func (failingAnnotator) Requirements() *plugin.Capabilities { return &plugin.Capabilities{} } 48 func (failingAnnotator) Annotate(ctx context.Context, input *annotator.ScanInput, results *inventory.Inventory) error { 49 return errors.New("some error") 50 } 51 52 func TestRun(t *testing.T) { 53 inv := &inventory.Inventory{ 54 Packages: []*extractor.Package{ 55 {Name: "package1", Version: "1.0", Locations: []string{"tmp/package.json"}}, 56 }, 57 } 58 59 copier := cpy.New( 60 cpy.Func(proto.Clone), 61 cpy.IgnoreAllUnexported(), 62 ) 63 64 tests := []struct { 65 desc string 66 cfg *annotator.Config 67 inv *inventory.Inventory 68 want []*plugin.Status 69 wantErr error 70 wantInv *inventory.Inventory // Inventory after annotation. 71 }{ 72 { 73 desc: "no_annotators", 74 cfg: &annotator.Config{}, 75 want: nil, 76 }, 77 { 78 desc: "annotator_modifies_inventory", 79 cfg: &annotator.Config{ 80 Annotators: []annotator.Annotator{cachedir.New()}, 81 }, 82 inv: inv, 83 want: []*plugin.Status{ 84 {Name: "vex/cachedir", Version: 0, Status: &plugin.ScanStatus{Status: plugin.ScanStatusSucceeded}}, 85 }, 86 wantInv: &inventory.Inventory{ 87 Packages: []*extractor.Package{ 88 { 89 Name: "package1", 90 Version: "1.0", 91 Locations: []string{"tmp/package.json"}, 92 ExploitabilitySignals: []*vex.PackageExploitabilitySignal{&vex.PackageExploitabilitySignal{ 93 Plugin: cachedir.Name, 94 Justification: vex.ComponentNotPresent, 95 MatchesAllVulns: true, 96 }}, 97 }, 98 }, 99 }, 100 }, 101 { 102 desc: "annotator_fails", 103 cfg: &annotator.Config{ 104 Annotators: []annotator.Annotator{&failingAnnotator{}}, 105 }, 106 want: []*plugin.Status{ 107 {Name: "failing-annotator", Version: 2, Status: &plugin.ScanStatus{Status: plugin.ScanStatusFailed, FailureReason: "some error"}}, 108 }, 109 }, 110 { 111 desc: "one_fails_one_succeeds", 112 cfg: &annotator.Config{ 113 Annotators: []annotator.Annotator{&succeedingAnnotator{}, &failingAnnotator{}}, 114 }, 115 want: []*plugin.Status{ 116 {Name: "succeeding-annotator", Version: 1, Status: &plugin.ScanStatus{Status: plugin.ScanStatusSucceeded}}, 117 {Name: "failing-annotator", Version: 2, Status: &plugin.ScanStatus{Status: plugin.ScanStatusFailed, FailureReason: "some error"}}, 118 }, 119 }, 120 } 121 122 for _, tc := range tests { 123 t.Run(tc.desc, func(t *testing.T) { 124 // Deep copy the inventory to avoid modifying the original inventory that is used in other tests. 125 inv := copier.Copy(tc.inv).(*inventory.Inventory) 126 got, err := annotator.Run(t.Context(), tc.cfg, inv) 127 if !cmp.Equal(err, tc.wantErr, cmpopts.EquateErrors()) { 128 t.Errorf("Run(%+v) error: got %v, want %v\n", tc.cfg, err, tc.wantErr) 129 } 130 if diff := cmp.Diff(tc.want, got); diff != "" { 131 t.Errorf("Run(%+v) returned an unexpected diff of statuses (-want +got): %v", tc.cfg, diff) 132 } 133 if diff := cmp.Diff(tc.wantInv, inv); diff != "" { 134 t.Errorf("Run(%+v) returned an unexpected diff of mutated inventory (-want +got): %v", tc.cfg, diff) 135 } 136 }) 137 } 138 }