github.com/quay/claircore@v1.5.28/datastore/postgres/querybuilder_test.go (about) 1 package postgres 2 3 import ( 4 "context" 5 "strings" 6 "testing" 7 8 "github.com/google/go-cmp/cmp" 9 "github.com/google/go-cmp/cmp/cmpopts" 10 "github.com/google/uuid" 11 "github.com/quay/zlog" 12 13 "github.com/quay/claircore" 14 "github.com/quay/claircore/datastore" 15 "github.com/quay/claircore/libvuln/driver" 16 "github.com/quay/claircore/pkg/pep440" 17 "github.com/quay/claircore/test" 18 "github.com/quay/claircore/test/integration" 19 pgtest "github.com/quay/claircore/test/postgres" 20 ) 21 22 func TestGetQueryBuilderDeterministicArgs(t *testing.T) { 23 const ( 24 preamble = `SELECT 25 "vuln"."id", "name", "description", "issued", "links", "severity", "normalized_severity", "package_name", "package_version", 26 "package_module", "package_arch", "package_kind", "dist_id", "dist_name", "dist_version", "dist_version_code_name", 27 "dist_version_id", "dist_arch", "dist_cpe", "dist_pretty_name", "arch_operation", "repo_name", "repo_key", 28 "repo_uri", "fixed_in_version", "vuln"."updater" 29 FROM "vuln" INNER JOIN "uo_vuln" ON ("vuln"."id" = "uo_vuln"."vuln") 30 INNER JOIN "latest_update_operations" ON ("latest_update_operations"."id" = "uo_vuln"."uo") 31 WHERE ` 32 epilogue = ` AND ("latest_update_operations"."kind" = 'vulnerability'))` 33 both = `(((("package_name" = 'package-0') AND ("package_kind" = 'binary')) OR (("package_name" = 'source-package-0') AND ("package_kind" = 'source'))) AND ` 34 noSource = `((("package_name" = 'package-0') AND ("package_kind" = 'binary')) AND ` 35 ) 36 var table = []struct { 37 // name of test 38 name string 39 // the expected query string returned 40 expectedQuery string 41 // the match expressions which contrain the query 42 matchExps []driver.MatchConstraint 43 dbFilter bool 44 // a method to returning the indexRecord for the getQueryBuilder method 45 indexRecord func() *claircore.IndexRecord 46 }{ 47 { 48 name: "NoSource,id", 49 expectedQuery: preamble + noSource + 50 `("dist_id" = 'did-0')` + epilogue, 51 matchExps: []driver.MatchConstraint{driver.DistributionDID}, 52 indexRecord: func() *claircore.IndexRecord { 53 pkgs := test.GenUniquePackages(1) 54 pkgs[0].Source = &claircore.Package{} // clear source field 55 dists := test.GenUniqueDistributions(1) 56 return &claircore.IndexRecord{ 57 Package: pkgs[0], 58 Distribution: dists[0], 59 } 60 }, 61 }, 62 { 63 name: "id", 64 expectedQuery: preamble + both + 65 `("dist_id" = 'did-0')` + epilogue, 66 matchExps: []driver.MatchConstraint{driver.DistributionDID}, 67 indexRecord: func() *claircore.IndexRecord { 68 pkgs := test.GenUniquePackages(1) 69 dists := test.GenUniqueDistributions(1) 70 return &claircore.IndexRecord{ 71 Package: pkgs[0], 72 Distribution: dists[0], 73 } 74 }, 75 }, 76 { 77 name: "id,version", 78 expectedQuery: preamble + both + 79 `("dist_id" = 'did-0') AND 80 ("dist_version" = 'version-0')` + epilogue, 81 matchExps: []driver.MatchConstraint{ 82 driver.DistributionDID, 83 driver.DistributionVersion, 84 }, 85 indexRecord: func() *claircore.IndexRecord { 86 pkgs := test.GenUniquePackages(1) 87 dists := test.GenUniqueDistributions(1) 88 return &claircore.IndexRecord{ 89 Package: pkgs[0], 90 Distribution: dists[0], 91 } 92 }, 93 }, 94 { 95 name: "id,version,version_id", 96 expectedQuery: preamble + both + 97 `("dist_id" = 'did-0') AND 98 ("dist_version" = 'version-0') AND 99 ("dist_version_id" = 'version-id-0')` + epilogue, 100 matchExps: []driver.MatchConstraint{ 101 driver.DistributionDID, 102 driver.DistributionVersion, 103 driver.DistributionVersionID, 104 }, 105 indexRecord: func() *claircore.IndexRecord { 106 pkgs := test.GenUniquePackages(1) 107 dists := test.GenUniqueDistributions(1) 108 return &claircore.IndexRecord{ 109 Package: pkgs[0], 110 Distribution: dists[0], 111 } 112 }, 113 }, 114 { 115 name: "id,version,version_id,version_code_name", 116 expectedQuery: preamble + both + 117 `("dist_id" = 'did-0') AND 118 ("dist_version" = 'version-0') AND 119 ("dist_version_id" = 'version-id-0') AND 120 ("dist_version_code_name" = 'version-code-name-0')` + epilogue, 121 matchExps: []driver.MatchConstraint{ 122 driver.DistributionDID, 123 driver.DistributionVersion, 124 driver.DistributionVersionID, 125 driver.DistributionVersionCodeName, 126 }, 127 indexRecord: func() *claircore.IndexRecord { 128 pkgs := test.GenUniquePackages(1) 129 dists := test.GenUniqueDistributions(1) 130 return &claircore.IndexRecord{ 131 Package: pkgs[0], 132 Distribution: dists[0], 133 } 134 }, 135 }, 136 { 137 name: "DatabaseFilter", 138 expectedQuery: preamble + both + 139 `(("version_kind" = '') AND 140 vulnerable_range @> '{0,0,0,0,0,0,0,0,0,0}'::int[])` + epilogue, 141 matchExps: []driver.MatchConstraint{}, 142 dbFilter: true, 143 indexRecord: func() *claircore.IndexRecord { 144 pkgs := test.GenUniquePackages(1) 145 dists := test.GenUniqueDistributions(1) 146 return &claircore.IndexRecord{ 147 Package: pkgs[0], 148 Distribution: dists[0], 149 } 150 }, 151 }, 152 { 153 name: "DatabaseFilterPython", 154 expectedQuery: preamble + both + 155 `(("version_kind" = 'pep440') AND 156 vulnerable_range @> '{0,1,20,3,0,0,0,0,0,0}'::int[])` + epilogue, 157 matchExps: []driver.MatchConstraint{}, 158 dbFilter: true, 159 indexRecord: func() *claircore.IndexRecord { 160 v, err := pep440.Parse("1.20.3") 161 if err != nil { 162 panic(err) 163 } 164 pkgs := test.GenUniquePackages(1) 165 pkgs[0].NormalizedVersion = v.Version() 166 dists := test.GenUniqueDistributions(1) 167 return &claircore.IndexRecord{ 168 Package: pkgs[0], 169 Distribution: dists[0], 170 } 171 }, 172 }, 173 { 174 name: "module-filter", 175 expectedQuery: preamble + noSource + 176 `("package_module" = 'module:0')` + epilogue, 177 matchExps: []driver.MatchConstraint{driver.PackageModule}, 178 indexRecord: func() *claircore.IndexRecord { 179 pkgs := test.GenUniquePackages(1) 180 pkgs[0].Source = &claircore.Package{} // clear source field 181 dists := test.GenUniqueDistributions(1) 182 return &claircore.IndexRecord{ 183 Package: pkgs[0], 184 Distribution: dists[0], 185 } 186 }, 187 }, 188 { 189 name: "repo_name", 190 expectedQuery: preamble + noSource + 191 `("repo_name" = 'repository-0')` + epilogue, 192 matchExps: []driver.MatchConstraint{driver.RepositoryName}, 193 indexRecord: func() *claircore.IndexRecord { 194 pkgs := test.GenUniquePackages(1) 195 pkgs[0].Source = &claircore.Package{} // clear source field 196 dists := test.GenUniqueDistributions(1) 197 repos := test.GenUniqueRepositories(1) 198 return &claircore.IndexRecord{ 199 Package: pkgs[0], 200 Distribution: dists[0], 201 Repository: repos[0], 202 } 203 }, 204 }, 205 } 206 207 // This is safe to do because SQL doesn't care about what whitespace is 208 // where. 209 // 210 // Also, it produces more intelligible diffs when things break. 211 normalizeWhitespace := cmpopts.AcyclicTransformer("normalizeWhitespace", strings.Fields) 212 213 for _, tt := range table { 214 t.Run(tt.name, func(t *testing.T) { 215 ir := tt.indexRecord() 216 opts := datastore.GetOpts{ 217 Matchers: tt.matchExps, 218 VersionFiltering: tt.dbFilter, 219 } 220 query, err := buildGetQuery(ir, &opts) 221 if err != nil { 222 t.Fatalf("failed to create query: %v", err) 223 } 224 t.Logf("got:\n%s", query) 225 if !cmp.Equal(query, tt.expectedQuery, normalizeWhitespace) { 226 t.Fatalf("%v", cmp.Diff(tt.expectedQuery, query, normalizeWhitespace)) 227 } 228 }) 229 } 230 } 231 232 type testCase struct { 233 Vulnerable int 234 Ops [][]*claircore.Vulnerability 235 Records []*claircore.IndexRecord 236 } 237 238 // TestLatestVulns checks that only the lastest update operations are 239 // considered when querying for vulns 240 func TestLatestVulns(t *testing.T) { 241 integration.NeedDB(t) 242 ctx := zlog.Test(context.Background(), t) 243 244 cases := []testCase{ 245 { 246 Vulnerable: 2, 247 Ops: [][]*claircore.Vulnerability{ 248 { 249 { 250 Updater: "test-updater", 251 Package: &claircore.Package{ 252 Name: "vi", 253 Version: "v2.0.0", 254 }, 255 }, 256 }, 257 { 258 { 259 Updater: "test-updater2", 260 Package: &claircore.Package{ 261 Name: "vi", 262 Version: "v3.0.0", 263 }, 264 }, 265 { 266 Updater: "test-updater2", 267 Package: &claircore.Package{ 268 Name: "vi", 269 Version: "v3.1.0", 270 }, 271 }, 272 }, 273 }, 274 Records: []*claircore.IndexRecord{ 275 { 276 Package: &claircore.Package{ 277 ID: "1", 278 Name: "vi", 279 Source: &claircore.Package{ 280 Name: "vi", 281 Version: "v1.0.0", 282 }, 283 }, 284 }, 285 }, 286 }, 287 } 288 289 pool := pgtest.TestMatcherDB(ctx, t) 290 store := NewMatcherStore(pool) 291 292 for _, tc := range cases { 293 for _, op := range tc.Ops { 294 _, err := store.UpdateVulnerabilities(ctx, updater, driver.Fingerprint(uuid.New().String()), op) 295 if err != nil { 296 t.Fatalf("failed to perform update for first op: %v", err) 297 } 298 } 299 300 res, err := store.Get(ctx, tc.Records, datastore.GetOpts{}) 301 if err != nil { 302 t.Fatalf("failed to get vulnerabilities: %v", err) 303 } 304 vulns := []*claircore.Vulnerability{} 305 for _, vs := range res { 306 vulns = append(vulns, vs...) 307 } 308 if len(vulns) != tc.Vulnerable { 309 t.Fatalf("wrong number of vulns, got %d want %d", len(vulns), tc.Vulnerable) 310 } 311 } 312 }