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 }