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  }