github.com/google/osv-scalibr@v0.4.1/binary/proto/finding_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 proto_test
    16  
    17  import (
    18  	"errors"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/google/go-cpy/cpy"
    23  	"github.com/google/osv-scalibr/binary/proto"
    24  	spb "github.com/google/osv-scalibr/binary/proto/scan_result_go_proto"
    25  	"github.com/google/osv-scalibr/inventory"
    26  	"github.com/google/osv-scalibr/inventory/vex"
    27  	"google.golang.org/protobuf/testing/protocmp"
    28  )
    29  
    30  var (
    31  	genericFindingStruct1 = &inventory.GenericFinding{
    32  		Adv: &inventory.GenericFindingAdvisory{
    33  			ID: &inventory.AdvisoryID{
    34  				Publisher: "CVE",
    35  				Reference: "CVE-1234",
    36  			},
    37  			Title:          "Title",
    38  			Description:    "Description",
    39  			Recommendation: "Recommendation",
    40  			Sev:            inventory.SeverityMedium,
    41  		},
    42  		Target: &inventory.GenericFindingTargetDetails{
    43  			Extra: "extra details",
    44  		},
    45  		Plugins: []string{"cve/cve-1234-finder", "cve/cve-1234-enricher"},
    46  		ExploitabilitySignals: []*vex.FindingExploitabilitySignal{{
    47  			Plugin:        "some-plugin",
    48  			Justification: vex.ComponentNotPresent,
    49  		}},
    50  	}
    51  
    52  	genericFindingProto1 = &spb.GenericFinding{
    53  		Adv: &spb.GenericFindingAdvisory{
    54  			Id: &spb.AdvisoryId{
    55  				Publisher: "CVE",
    56  				Reference: "CVE-1234",
    57  			},
    58  			Title:          "Title",
    59  			Description:    "Description",
    60  			Recommendation: "Recommendation",
    61  			Sev:            spb.SeverityEnum_MEDIUM,
    62  		},
    63  		Target: &spb.GenericFindingTargetDetails{
    64  			Extra: "extra details",
    65  		},
    66  		Plugins: []string{"cve/cve-1234-finder", "cve/cve-1234-enricher"},
    67  		ExploitabilitySignals: []*spb.FindingExploitabilitySignal{{
    68  			Plugin:        "some-plugin",
    69  			Justification: spb.VexJustification_COMPONENT_NOT_PRESENT,
    70  		}},
    71  	}
    72  )
    73  
    74  func TestGenericFindingToProto(t *testing.T) {
    75  	copier := cpy.New(
    76  		cpy.IgnoreAllUnexported(),
    77  	)
    78  
    79  	testCases := []struct {
    80  		desc    string
    81  		finding *inventory.GenericFinding
    82  		want    *spb.GenericFinding
    83  		wantErr error
    84  	}{
    85  		{
    86  			desc:    "success",
    87  			finding: genericFindingStruct1,
    88  			want:    genericFindingProto1,
    89  		},
    90  		{
    91  			desc:    "nil",
    92  			finding: nil,
    93  			want:    nil,
    94  		},
    95  		{
    96  			desc: "missing_advisory",
    97  			finding: func(f *inventory.GenericFinding) *inventory.GenericFinding {
    98  				f = copier.Copy(f).(*inventory.GenericFinding)
    99  				f.Adv = nil
   100  				return f
   101  			}(genericFindingStruct1),
   102  			want:    nil,
   103  			wantErr: proto.ErrAdvisoryMissing,
   104  		},
   105  		{
   106  			desc: "missing_advisory_ID",
   107  			finding: func(f *inventory.GenericFinding) *inventory.GenericFinding {
   108  				f = copier.Copy(f).(*inventory.GenericFinding)
   109  				f.Adv.ID = nil
   110  				return f
   111  			}(genericFindingStruct1),
   112  			want:    nil,
   113  			wantErr: proto.ErrAdvisoryIDMissing,
   114  		},
   115  	}
   116  
   117  	for _, tc := range testCases {
   118  		t.Run(tc.desc, func(t *testing.T) {
   119  			got, err := proto.GenericFindingToProto(tc.finding)
   120  			if !errors.Is(err, tc.wantErr) {
   121  				t.Errorf("GenericFindingToProto(%v) returned error %v, want error %v", tc.finding, err, tc.wantErr)
   122  			}
   123  			if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   124  				t.Fatalf("GenericFindingToProto(%v) returned diff (-want +got):\n%s", tc.finding, diff)
   125  			}
   126  
   127  			// No need to test the reverse conversion if the result is nil.
   128  			if got == nil {
   129  				return
   130  			}
   131  
   132  			// Test the reverse conversion for completeness.
   133  			gotPB, err := proto.GenericFindingToStruct(got)
   134  			if err != nil {
   135  				t.Fatalf("GenericFindingToStruct(%v) returned error %v, want nil", got, err)
   136  			}
   137  			if diff := cmp.Diff(tc.finding, gotPB, protocmp.Transform()); diff != "" {
   138  				t.Fatalf("GenericFindingToStruct(%v) returned diff (-want +got):\n%s", got, diff)
   139  			}
   140  		})
   141  	}
   142  }
   143  
   144  func TestGenericFindingToStruct(t *testing.T) {
   145  	copier := cpy.New(
   146  		cpy.IgnoreAllUnexported(),
   147  	)
   148  
   149  	testCases := []struct {
   150  		desc    string
   151  		finding *spb.GenericFinding
   152  		want    *inventory.GenericFinding
   153  		wantErr error
   154  	}{
   155  		{
   156  			desc:    "success",
   157  			finding: genericFindingProto1,
   158  			want:    genericFindingStruct1,
   159  		},
   160  		{
   161  			desc:    "nil",
   162  			finding: nil,
   163  			want:    nil,
   164  		},
   165  		{
   166  			desc: "missing_advisory",
   167  			finding: func(f *spb.GenericFinding) *spb.GenericFinding {
   168  				f = copier.Copy(f).(*spb.GenericFinding)
   169  				f.Adv = nil
   170  				return f
   171  			}(genericFindingProto1),
   172  			want:    nil,
   173  			wantErr: proto.ErrAdvisoryMissing,
   174  		},
   175  		{
   176  			desc: "missing_advisory_ID",
   177  			finding: func(f *spb.GenericFinding) *spb.GenericFinding {
   178  				f = copier.Copy(f).(*spb.GenericFinding)
   179  				f.Adv.Id = nil
   180  				return f
   181  			}(genericFindingProto1),
   182  			want:    nil,
   183  			wantErr: proto.ErrAdvisoryIDMissing,
   184  		},
   185  	}
   186  
   187  	for _, tc := range testCases {
   188  		t.Run(tc.desc, func(t *testing.T) {
   189  			got, err := proto.GenericFindingToStruct(tc.finding)
   190  			if !errors.Is(err, tc.wantErr) {
   191  				t.Fatalf("GenericFindingToStruct(%v) returned error %v, want error %v", tc.finding, err, tc.wantErr)
   192  			}
   193  			if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   194  				t.Fatalf("GenericFindingToStruct(%v) returned diff (-want +got):\n%s", tc.finding, diff)
   195  			}
   196  
   197  			// No need to test the reverse conversion if the result is nil.
   198  			if got == nil {
   199  				return
   200  			}
   201  
   202  			// Test the reverse conversion for completeness.
   203  			gotPB, err := proto.GenericFindingToProto(got)
   204  			if err != nil {
   205  				t.Fatalf("GenericFindingToProto(%v) returned error %v, want nil", got, err)
   206  			}
   207  			if diff := cmp.Diff(tc.finding, gotPB, protocmp.Transform()); diff != "" {
   208  				t.Fatalf("GenericFindingToProto(%v) returned diff (-want +got):\n%s", got, diff)
   209  			}
   210  		})
   211  	}
   212  }