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  }