github.com/google/osv-scalibr@v0.4.1/binary/proto/inventory_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-cmp/cmp/cmpopts"
    23  	"github.com/google/osv-scalibr/binary/proto"
    24  	"github.com/google/osv-scalibr/extractor"
    25  	"github.com/google/osv-scalibr/inventory"
    26  	"google.golang.org/protobuf/testing/protocmp"
    27  
    28  	pb "github.com/google/osv-scalibr/binary/proto/scan_result_go_proto"
    29  	osvpb "github.com/ossf/osv-schema/bindings/go/osvschema"
    30  )
    31  
    32  func TestInventoryToProto(t *testing.T) {
    33  	testCases := []struct {
    34  		desc    string
    35  		inv     *inventory.Inventory
    36  		want    *pb.Inventory
    37  		wantErr error
    38  	}{
    39  		{
    40  			desc: "nil",
    41  			inv:  nil,
    42  			want: nil,
    43  		},
    44  		{
    45  			desc: "empty",
    46  			inv:  &inventory.Inventory{},
    47  			want: &pb.Inventory{},
    48  		},
    49  		{
    50  			desc: "success",
    51  			inv: &inventory.Inventory{
    52  				Packages: []*extractor.Package{
    53  					purlDPKGAnnotationPackage,
    54  					pkgWithLayerStruct,
    55  				},
    56  				PackageVulns: []*inventory.PackageVuln{
    57  					pkgVulnStruct1,
    58  				},
    59  				GenericFindings: []*inventory.GenericFinding{
    60  					genericFindingStruct1,
    61  				},
    62  				Secrets: []*inventory.Secret{
    63  					secretGCPSAKStruct1,
    64  				},
    65  				ContainerImageMetadata: []*extractor.ContainerImageMetadata{
    66  					cimStructForTest,
    67  				},
    68  			},
    69  			want: &pb.Inventory{
    70  				Packages: []*pb.Package{
    71  					purlDPKGAnnotationPackageProto,
    72  					pkgWithLayerProto,
    73  				},
    74  				PackageVulns: []*pb.PackageVuln{
    75  					pkgVulnProto1,
    76  				},
    77  				GenericFindings: []*pb.GenericFinding{
    78  					genericFindingProto1,
    79  				},
    80  				Secrets: []*pb.Secret{
    81  					secretGCPSAKProto1,
    82  				},
    83  				ContainerImageMetadata: []*pb.ContainerImageMetadata{
    84  					cimProtoForTest,
    85  				},
    86  			},
    87  		},
    88  	}
    89  
    90  	for _, tc := range testCases {
    91  		t.Run(tc.desc, func(t *testing.T) {
    92  			got, err := proto.InventoryToProto(tc.inv)
    93  			if !errors.Is(err, tc.wantErr) {
    94  				t.Errorf("InventoryToProto(%v) returned error %v, want error %v", tc.inv, err, tc.wantErr)
    95  			}
    96  			opts := append([]cmp.Option{
    97  				protocmp.Transform(),
    98  				protocmp.IgnoreFields(&pb.PackageVuln{}, "package_id"),
    99  				cmpopts.EquateEmpty(),
   100  			}, pkgOpts...)
   101  			if diff := cmp.Diff(tc.want, got, opts...); diff != "" {
   102  				t.Errorf("InventoryToProto(%v) returned diff (-want +got):\n%s", tc.inv, diff)
   103  			}
   104  
   105  			// Test the reverse conversion for completeness.
   106  			gotInv := proto.InventoryToStruct(got)
   107  			opts = []cmp.Option{
   108  				protocmp.Transform(),
   109  				cmpopts.IgnoreFields(extractor.LayerMetadata{}, "ParentContainer"),
   110  			}
   111  			if diff := cmp.Diff(tc.inv, gotInv, opts...); diff != "" {
   112  				t.Errorf("InventoryToStruct(%v) returned diff (-want +got):\n%s", gotInv, diff)
   113  			}
   114  		})
   115  	}
   116  }
   117  
   118  // We do it in a separate test because we don't want to test the reverse operation.
   119  func TestInventoryToProtoInvalidPackage(t *testing.T) {
   120  	testCases := []struct {
   121  		desc    string
   122  		inv     *inventory.Inventory
   123  		want    *pb.Inventory
   124  		wantErr error
   125  	}{
   126  		{
   127  			desc: "missing_package",
   128  			inv: &inventory.Inventory{
   129  				PackageVulns: []*inventory.PackageVuln{
   130  					pkgVulnStruct1,
   131  				},
   132  			},
   133  			wantErr: cmpopts.AnyError,
   134  			want:    nil,
   135  		},
   136  	}
   137  	for _, tc := range testCases {
   138  		t.Run(tc.desc, func(t *testing.T) {
   139  			got, err := proto.InventoryToProto(tc.inv)
   140  			if diff := cmp.Diff(tc.wantErr, err, cmpopts.EquateErrors()); diff != "" {
   141  				t.Errorf("InventoryToProto() error mismatch (-want +got):\n%s", diff)
   142  			}
   143  
   144  			if diff := cmp.Diff(tc.want, got, pkgOpts...); diff != "" {
   145  				t.Errorf("InventoryToProto(%v) returned diff (-want +got):\n%s", tc.inv, diff)
   146  			}
   147  		})
   148  	}
   149  }
   150  
   151  func TestInventoryToStruct(t *testing.T) {
   152  	pkgWithIDProto :=
   153  		&pb.Package{
   154  			Id:        "1234567890",
   155  			Name:      "software",
   156  			Version:   "1.0.0",
   157  			Locations: []string{"/file1"},
   158  			Plugins:   []string{"os/dpkg"},
   159  		}
   160  	pkgStruct :=
   161  		&extractor.Package{
   162  			Name:      "software",
   163  			Version:   "1.0.0",
   164  			Locations: []string{"/file1"},
   165  			Plugins:   []string{"os/dpkg"},
   166  		}
   167  	pkgVulnProto := &pb.PackageVuln{
   168  		Vuln:      &osvpb.Vulnerability{Id: "GHSA-1"},
   169  		PackageId: pkgWithIDProto.Id,
   170  		Plugins:   []string{"plugin1"},
   171  	}
   172  	pkgVulnStruct := &inventory.PackageVuln{
   173  		Vulnerability: &osvpb.Vulnerability{Id: "GHSA-1"},
   174  		Package:       pkgStruct,
   175  		Plugins:       []string{"plugin1"},
   176  	}
   177  	testCases := []struct {
   178  		desc string
   179  		inv  *pb.Inventory
   180  		want *inventory.Inventory
   181  	}{
   182  		{
   183  			desc: "nil",
   184  			inv:  nil,
   185  			want: nil,
   186  		},
   187  		{
   188  			desc: "empty",
   189  			inv:  &pb.Inventory{},
   190  			want: &inventory.Inventory{},
   191  		},
   192  		{
   193  			desc: "success",
   194  			inv: &pb.Inventory{
   195  				Packages: []*pb.Package{
   196  					purlDPKGAnnotationPackageProto,
   197  					pkgWithLayerProto,
   198  					pkgWithIDProto,
   199  				},
   200  				PackageVulns: []*pb.PackageVuln{
   201  					pkgVulnProto,
   202  				},
   203  				GenericFindings: []*pb.GenericFinding{
   204  					genericFindingProto1,
   205  				},
   206  				Secrets: []*pb.Secret{
   207  					secretGCPSAKProto1,
   208  				},
   209  				ContainerImageMetadata: []*pb.ContainerImageMetadata{
   210  					cimProtoForTest,
   211  				},
   212  			},
   213  			want: &inventory.Inventory{
   214  				Packages: []*extractor.Package{
   215  					purlDPKGAnnotationPackage,
   216  					pkgWithLayerStruct,
   217  					pkgStruct,
   218  				},
   219  				PackageVulns: []*inventory.PackageVuln{
   220  					pkgVulnStruct,
   221  				},
   222  				GenericFindings: []*inventory.GenericFinding{
   223  					genericFindingStruct1,
   224  				},
   225  				Secrets: []*inventory.Secret{
   226  					secretGCPSAKStruct1,
   227  				},
   228  				ContainerImageMetadata: []*extractor.ContainerImageMetadata{
   229  					cimStructForTest,
   230  				},
   231  			},
   232  		},
   233  	}
   234  
   235  	for _, tc := range testCases {
   236  		t.Run(tc.desc, func(t *testing.T) {
   237  			got := proto.InventoryToStruct(tc.inv)
   238  			opts := []cmp.Option{
   239  				protocmp.Transform(),
   240  				cmpopts.IgnoreFields(extractor.LayerMetadata{}, "ParentContainer"),
   241  				cmpopts.EquateEmpty(),
   242  			}
   243  			if diff := cmp.Diff(tc.want, got, opts...); diff != "" {
   244  				t.Errorf("InventoryToStruct(%v) returned diff (-want +got):\n%s", tc.inv, diff)
   245  			}
   246  
   247  			// Test the reverse conversion for completeness.
   248  			gotPB, err := proto.InventoryToProto(got)
   249  			if err != nil {
   250  				t.Fatalf("InventoryToProto(%v) returned error %v, want nil", got, err)
   251  			}
   252  			revOpts := append([]cmp.Option{
   253  				protocmp.Transform(),
   254  				protocmp.IgnoreFields(&pb.PackageVuln{}, "package_id"),
   255  				cmpopts.EquateEmpty(),
   256  			}, pkgOpts...)
   257  			if diff := cmp.Diff(tc.inv, gotPB, revOpts...); diff != "" {
   258  				t.Errorf("InventoryToProto(%v) returned diff (-want +got):\n%s", got, diff)
   259  			}
   260  		})
   261  	}
   262  }
   263  
   264  // We do it in a separate test because the conversion is lossy and we don't want
   265  // to test the reverse operation.
   266  func TestInventoryToStructInvalidPkgVuln(t *testing.T) {
   267  	testCases := []struct {
   268  		desc string
   269  		inv  *pb.Inventory
   270  		want *inventory.Inventory
   271  	}{
   272  		{
   273  			desc: "package_without_id",
   274  			inv: &pb.Inventory{
   275  				Packages:     []*pb.Package{{Name: "no_id_pkg"}},
   276  				PackageVulns: []*pb.PackageVuln{{PackageId: "some_id"}},
   277  			},
   278  			want: &inventory.Inventory{
   279  				Packages: []*extractor.Package{{Name: "no_id_pkg"}},
   280  			},
   281  		},
   282  		{
   283  			desc: "packages_with_duplicate_id",
   284  			inv: &pb.Inventory{
   285  				Packages:     []*pb.Package{{Name: "pkg1", Id: "pkg"}, {Name: "pkg2", Id: "pkg"}},
   286  				PackageVulns: []*pb.PackageVuln{{PackageId: "pkg"}},
   287  			},
   288  			want: &inventory.Inventory{
   289  				Packages:     []*extractor.Package{{Name: "pkg1"}, {Name: "pkg2"}},
   290  				PackageVulns: []*inventory.PackageVuln{{Package: &extractor.Package{Name: "pkg1"}}},
   291  			},
   292  		},
   293  		{
   294  			desc: "pkgvuln_with_no_packages",
   295  			inv: &pb.Inventory{
   296  				PackageVulns: []*pb.PackageVuln{{PackageId: "some_id"}},
   297  			},
   298  			want: &inventory.Inventory{},
   299  		},
   300  	}
   301  
   302  	for _, tc := range testCases {
   303  		t.Run(tc.desc, func(t *testing.T) {
   304  			got := proto.InventoryToStruct(tc.inv)
   305  			if diff := cmp.Diff(tc.want, got, cmpopts.IgnoreFields(extractor.LayerMetadata{}, "ParentContainer"), protocmp.Transform()); diff != "" {
   306  				t.Fatalf("InventoryToStruct(%v) returned diff (-want +got):\n%s", tc.inv, diff)
   307  			}
   308  		})
   309  	}
   310  }