github.com/google/osv-scalibr@v0.4.1/annotator/misc/dpkgsource/dpkgsource_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 dpkgsource_test 16 17 import ( 18 "context" 19 "testing" 20 21 "github.com/google/go-cmp/cmp" 22 "github.com/google/go-cpy/cpy" 23 "github.com/google/osv-scalibr/annotator" 24 "github.com/google/osv-scalibr/annotator/misc/dpkgsource" 25 "github.com/google/osv-scalibr/extractor" 26 dpkgmetadata "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata" 27 "github.com/google/osv-scalibr/inventory" 28 "github.com/google/osv-scalibr/purl" 29 "google.golang.org/protobuf/proto" 30 ) 31 32 func TestAnnotate_DPKGSource(t *testing.T) { 33 copier := cpy.New( 34 cpy.Func(proto.Clone), 35 cpy.IgnoreAllUnexported(), 36 ) 37 38 origFetchAptCachePolicy := dpkgsource.FetchAptCachePolicy 39 defer func() { dpkgsource.FetchAptCachePolicy = origFetchAptCachePolicy }() 40 41 mockPolicyResults := map[string]string{ 42 "libfoo": "http://deb.debian.org/debian", 43 "libbar": "/var/lib/dpkg/status", 44 } 45 dpkgsource.FetchAptCachePolicy = mockGetAptCachePolicy(mockPolicyResults) 46 47 annotatorInstance := dpkgsource.New() 48 49 testCases := []struct { 50 name string 51 input *inventory.Inventory 52 want *inventory.Inventory 53 }{ 54 { 55 name: "debian_package_remote_source", 56 input: &inventory.Inventory{ 57 Packages: []*extractor.Package{ 58 { 59 Name: "libfoo", 60 Version: "1.0", 61 PURLType: purl.TypeDebian, 62 Metadata: &dpkgmetadata.Metadata{}, 63 }, 64 }, 65 }, 66 want: &inventory.Inventory{ 67 Packages: []*extractor.Package{ 68 { 69 Name: "libfoo", 70 Version: "1.0", 71 PURLType: purl.TypeDebian, 72 Metadata: &dpkgmetadata.Metadata{ 73 PackageSource: "http://deb.debian.org/debian", 74 }, 75 }, 76 }, 77 }, 78 }, 79 { 80 name: "debian_package_local_source", 81 input: &inventory.Inventory{ 82 Packages: []*extractor.Package{ 83 { 84 Name: "libbar", 85 Version: "1.0", 86 PURLType: purl.TypeDebian, 87 Metadata: &dpkgmetadata.Metadata{}, 88 }, 89 }, 90 }, 91 want: &inventory.Inventory{ 92 Packages: []*extractor.Package{ 93 { 94 Name: "libbar", 95 Version: "1.0", 96 PURLType: purl.TypeDebian, 97 Metadata: &dpkgmetadata.Metadata{ 98 PackageSource: "/var/lib/dpkg/status", 99 }, 100 }, 101 }, 102 }, 103 }, 104 { 105 name: "not_debian_package", 106 input: &inventory.Inventory{ 107 Packages: []*extractor.Package{ 108 { 109 Name: "not-deb", 110 Version: "1.0", 111 PURLType: purl.TypeNPM, 112 Metadata: &dpkgmetadata.Metadata{}, 113 }, 114 }, 115 }, 116 want: &inventory.Inventory{ 117 Packages: []*extractor.Package{ 118 { 119 Name: "not-deb", 120 Version: "1.0", 121 PURLType: purl.TypeNPM, 122 Metadata: &dpkgmetadata.Metadata{}, 123 }, 124 }, 125 }, 126 }, 127 { 128 name: "debian_package_missing_source", 129 input: &inventory.Inventory{ 130 Packages: []*extractor.Package{ 131 { 132 Name: "missing-source", 133 Version: "1.0", 134 PURLType: purl.TypeDebian, 135 Metadata: &dpkgmetadata.Metadata{}, 136 }, 137 }, 138 }, 139 want: &inventory.Inventory{ 140 Packages: []*extractor.Package{ 141 { 142 Name: "missing-source", 143 Version: "1.0", 144 PURLType: purl.TypeDebian, 145 Metadata: &dpkgmetadata.Metadata{ 146 PackageSource: "unknown", 147 }, 148 }, 149 }, 150 }, 151 }, 152 } 153 154 for _, tt := range testCases { 155 t.Run(tt.name, func(t *testing.T) { 156 inputPackagesCopy := make([]*extractor.Package, len(tt.input.Packages)) 157 for i, pkg := range tt.input.Packages { 158 inputPackagesCopy[i] = copier.Copy(pkg).(*extractor.Package) 159 } 160 inv := &inventory.Inventory{Packages: inputPackagesCopy} 161 162 err := annotatorInstance.Annotate(t.Context(), &annotator.ScanInput{}, inv) 163 if err != nil { 164 t.Fatalf("Annotate() unexpected error: %v", err) 165 } 166 167 if diff := cmp.Diff(tt.want, inv); diff != "" { 168 t.Errorf("Annotate() unexpected diff (-want +got):\n%s", diff) 169 } 170 }) 171 } 172 } 173 174 func TestMapPackageToSource(t *testing.T) { 175 tests := []struct { 176 name string 177 aptCacheOutput string 178 want map[string]string 179 }{ 180 { 181 name: "empty_input", 182 aptCacheOutput: "", 183 want: map[string]string{}, 184 }, 185 { 186 name: "single_package", 187 aptCacheOutput: `mypackage: 188 Installed: 20.1.1 189 Candidate: 20.1.1 190 Version table: 191 *** 20.1.1 192 500 http://deb.debian.org/debian stable/main amd64 Packages 193 `, 194 want: map[string]string{"mypackage": "http://deb.debian.org/debian"}, 195 }, 196 { 197 name: "multiple_packages", 198 aptCacheOutput: `pkg1: 199 Installed: 1.0 200 *** 1.0 500 201 500 http://deb.debian.org/debian stable/main amd64 Packages 202 pkg2: 203 Installed: 2.1 204 *** 2.1 900 205 100 http://security.debian.org/debian-security stretch/updates/main amd64 Packages 206 `, 207 want: map[string]string{ 208 "pkg1": "http://deb.debian.org/debian", 209 "pkg2": "http://security.debian.org/debian-security", 210 }, 211 }, 212 { 213 name: "multiple_repositories", 214 aptCacheOutput: `mypackage: 215 Installed: 1.5 216 *** 1.5 500 217 500 http://deb.debian.org/debian stable/main amd64 Packages 218 100 http://archive.debian.org/debian oldstable/main amd64 Packages 219 `, 220 want: map[string]string{"mypackage": "http://deb.debian.org/debian"}, 221 }, 222 { 223 name: "missing_installed_version", 224 aptCacheOutput: `mypackage: 225 Candidate: 1.0 226 Version table: 227 1.0 500 228 500 http://deb.debian.org/debian stable/main amd64 Packages 229 `, 230 want: map[string]string{}, 231 }, 232 { 233 name: "whitespace_variation", 234 aptCacheOutput: `mypackage: 235 Installed: 20.1.1 236 Candidate: 20.1.1 237 Version table: 238 *** 20.1.1 239 500 http://deb.debian.org/debian stable/main amd64 Packages 240 `, 241 want: map[string]string{"mypackage": "http://deb.debian.org/debian"}, 242 }, 243 { 244 name: "package_name_special_chars", 245 aptCacheOutput: `libfoo-dev: 246 Installed: 1.0-1 247 *** 1.0-1 500 248 500 http://deb.debian.org/debian stable/main amd64 Packages 249 `, 250 want: map[string]string{"libfoo-dev": "http://deb.debian.org/debian"}, 251 }, 252 { 253 name: "input_ends_after_installed_version", 254 aptCacheOutput: `mypackage: 255 Installed: 1.0 256 *** 1.0 500`, 257 want: map[string]string{"mypackage": "unknown"}, 258 }, 259 { 260 name: "repository_source_single_field", 261 aptCacheOutput: `mypackage: 262 Installed: 1.0 263 *** 1.0 500 264 onlyone`, 265 want: map[string]string{"mypackage": "unknown"}, 266 }, 267 { 268 name: "single_malformed_source", 269 aptCacheOutput: `pkg1: 270 Installed: 1.0 271 *** 1.0 900 272 malformedline 273 274 pkg2: 275 Installed: 2.1 276 *** 2.1 900 277 500 http://deb.debian.org/debian stable/main amd64 Packages 278 `, 279 want: map[string]string{ 280 "pkg1": "unknown", 281 "pkg2": "http://deb.debian.org/debian", 282 }, 283 }, 284 } 285 286 for _, tt := range tests { 287 t.Run(tt.name, func(t *testing.T) { 288 got, err := dpkgsource.MapPackageToSource(t.Context(), tt.aptCacheOutput) 289 if err != nil { 290 t.Fatalf("MapPackageToSource(%q) returned an unexpected error: %v", tt.aptCacheOutput, err) 291 } 292 293 if diff := cmp.Diff(tt.want, got); diff != "" { 294 t.Errorf("MapPackageToSource(%q) returned an unexpected diff (-want +got):\n%s", tt.aptCacheOutput, diff) 295 } 296 }) 297 } 298 } 299 300 // Mock implementation for fetchAptCachePolicy 301 func mockGetAptCachePolicy(mockResults map[string]string) func(context.Context, []*extractor.Package) (map[string]string, error) { 302 return func(ctx context.Context, pkgs []*extractor.Package) (map[string]string, error) { 303 return mockResults, nil 304 } 305 }