github.com/quay/claircore@v1.5.28/updater/updater_test.go (about)

     1  package updater
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"context"
     7  	"crypto/sha256"
     8  	"encoding/hex"
     9  	"encoding/json"
    10  	"io"
    11  	"io/fs"
    12  	"net/http"
    13  	"path"
    14  	"reflect"
    15  	"testing"
    16  
    17  	"github.com/golang/mock/gomock"
    18  	"github.com/google/uuid"
    19  	"github.com/quay/zlog"
    20  
    21  	mock_updater "github.com/quay/claircore/test/mock/updater"
    22  	mock_driver "github.com/quay/claircore/test/mock/updater/driver/v1"
    23  	"github.com/quay/claircore/updater/driver/v1"
    24  )
    25  
    26  func TestNew(t *testing.T) {
    27  	ctx := zlog.Test(context.Background(), t)
    28  	t.Run("MissingStore", func(t *testing.T) {
    29  		ctx := zlog.Test(ctx, t)
    30  		u, err := New(ctx, &Options{
    31  			Client: &http.Client{},
    32  		})
    33  		t.Log(err)
    34  		if err == nil {
    35  			t.Error("unexpected success")
    36  			if err := u.Close(); err != nil {
    37  				t.Error(err)
    38  			}
    39  		}
    40  	})
    41  	t.Run("MissingClient", func(t *testing.T) {
    42  		ctx := zlog.Test(ctx, t)
    43  		u, err := New(ctx, &Options{
    44  			Store: mock_updater.NewMockStore(nil),
    45  		})
    46  		t.Log(err)
    47  		if err == nil {
    48  			t.Error("unexpected success")
    49  			if err := u.Close(); err != nil {
    50  				t.Error(err)
    51  			}
    52  		}
    53  	})
    54  }
    55  
    56  type mockparser struct {
    57  	driver.Updater
    58  	driver.VulnerabilityParser
    59  	driver.EnrichmentParser
    60  }
    61  
    62  const (
    63  	vulnerabilityFile = `vulnerability.json`
    64  	enrichmentFile    = `enrichment.json`
    65  )
    66  
    67  var (
    68  	matchCtx    = gomock.AssignableToTypeOf(reflect.TypeOf((*context.Context)(nil)).Elem())
    69  	matchFp     = gomock.AssignableToTypeOf(reflect.TypeOf(driver.Fingerprint("")))
    70  	matchZip    = gomock.AssignableToTypeOf(reflect.TypeOf((*zip.Writer)(nil)))
    71  	matchClient = gomock.AssignableToTypeOf(reflect.TypeOf((*http.Client)(nil)))
    72  	matchFS     = gomock.AssignableToTypeOf(reflect.TypeOf((*fs.FS)(nil)).Elem())
    73  	matchUUID   = gomock.AssignableToTypeOf(reflect.TypeOf(uuid.Nil))
    74  )
    75  
    76  func fetchFunc(t *testing.T, es []driver.EnrichmentRecord, vs *driver.ParsedVulnerabilities) func(context.Context, *zip.Writer, driver.Fingerprint, *http.Client) (driver.Fingerprint, error) {
    77  	return func(_ context.Context, z *zip.Writer, fp driver.Fingerprint, _ *http.Client) (driver.Fingerprint, error) {
    78  		h := sha256.New()
    79  		var vb, eb bytes.Buffer
    80  		if err := json.NewEncoder(io.MultiWriter(h, &vb)).Encode(vs); err != nil {
    81  			return fp, err
    82  		}
    83  		if err := json.NewEncoder(io.MultiWriter(h, &eb)).Encode(es); err != nil {
    84  			return fp, err
    85  		}
    86  		cfp := driver.Fingerprint(hex.EncodeToString(h.Sum(nil)))
    87  		t.Logf("prev fp: %q", fp)
    88  		t.Logf("calc fp: %q", cfp)
    89  		if fp == cfp {
    90  			return fp, driver.ErrUnchanged
    91  		}
    92  
    93  		w, err := z.Create(vulnerabilityFile)
    94  		if err != nil {
    95  			return fp, err
    96  		}
    97  		if _, err := io.Copy(w, &vb); err != nil {
    98  			return fp, err
    99  		}
   100  		w, err = z.Create(enrichmentFile)
   101  		if err != nil {
   102  			return fp, err
   103  		}
   104  		if _, err := io.Copy(w, &eb); err != nil {
   105  			return fp, err
   106  		}
   107  		return cfp, nil
   108  	}
   109  }
   110  
   111  func parseVuln(_ context.Context, in fs.FS) (*driver.ParsedVulnerabilities, error) {
   112  	var r driver.ParsedVulnerabilities
   113  	f, err := in.Open(vulnerabilityFile)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	if err := json.NewDecoder(f).Decode(&r); err != nil {
   118  		return nil, err
   119  	}
   120  	return &r, f.Close()
   121  }
   122  
   123  func parseEnrich(_ context.Context, in fs.FS) (r []driver.EnrichmentRecord, err error) {
   124  	f, err := in.Open(enrichmentFile)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	if err := json.NewDecoder(f).Decode(&r); err != nil {
   129  		return nil, err
   130  	}
   131  	return r, f.Close()
   132  }
   133  
   134  func TestRun(t *testing.T) {
   135  	ctx := zlog.Test(context.Background(), t)
   136  	ctl := gomock.NewController(t)
   137  	n := path.Base(t.Name())
   138  	vs := &driver.ParsedVulnerabilities{
   139  		Updater:       t.Name(),
   140  		Vulnerability: []driver.Vulnerability{{}},
   141  	}
   142  	es := []driver.EnrichmentRecord{
   143  		{Enrichment: json.RawMessage("null")},
   144  	}
   145  
   146  	upd := mock_driver.NewMockUpdater(ctl)
   147  	upd.EXPECT().Name().
   148  		MinTimes(2).
   149  		Return(n)
   150  	upd.EXPECT().Fetch(matchCtx, matchZip, matchFp, matchClient).
   151  		Times(2).
   152  		DoAndReturn(fetchFunc(t, es, vs))
   153  	vp := mock_driver.NewMockVulnerabilityParser(ctl)
   154  	vp.EXPECT().ParseVulnerability(matchCtx, matchFS).
   155  		Times(1).
   156  		DoAndReturn(parseVuln)
   157  	ep := mock_driver.NewMockEnrichmentParser(ctl)
   158  	ep.EXPECT().ParseEnrichment(matchCtx, matchFS).
   159  		Times(1).
   160  		DoAndReturn(parseEnrich)
   161  	fac := mock_driver.NewMockUpdaterFactory(ctl)
   162  	fac.EXPECT().Name().
   163  		MinTimes(2).
   164  		Return(n)
   165  	fac.EXPECT().Create(matchCtx, gomock.Nil()).
   166  		Times(2).
   167  		Return([]driver.Updater{&mockparser{
   168  			Updater:             upd,
   169  			VulnerabilityParser: vp,
   170  			EnrichmentParser:    ep,
   171  		}}, nil)
   172  	store := mock_updater.NewMockStore(ctl)
   173  	var ops []driver.UpdateOperation
   174  	store.EXPECT().UpdateVulnerabilities(matchCtx, matchUUID, gomock.Eq(n), matchFp, gomock.Eq(vs)).
   175  		Times(1).
   176  		DoAndReturn(func(_ context.Context, ref uuid.UUID, name string, fp driver.Fingerprint, _ *driver.ParsedVulnerabilities) error {
   177  			t.Log(fp)
   178  			ops = append(ops, driver.UpdateOperation{
   179  				Fingerprint: fp,
   180  				Updater:     name,
   181  				Kind:        driver.VulnerabilityKind,
   182  				Ref:         ref,
   183  			})
   184  			return nil
   185  		})
   186  	store.EXPECT().UpdateEnrichments(matchCtx, matchUUID, gomock.Eq(n), matchFp, gomock.Eq(es)).
   187  		Times(1).
   188  		DoAndReturn(func(_ context.Context, ref uuid.UUID, name string, fp driver.Fingerprint, _ []driver.EnrichmentRecord) error {
   189  			t.Log(fp)
   190  			ops = append(ops, driver.UpdateOperation{
   191  				Fingerprint: fp,
   192  				Updater:     name,
   193  				Kind:        driver.EnrichmentKind,
   194  				Ref:         ref,
   195  			})
   196  			return nil
   197  		})
   198  	store.EXPECT().GetLatestUpdateOperations(matchCtx).
   199  		Times(2).
   200  		DoAndReturn(func(context.Context) ([]driver.UpdateOperation, error) {
   201  			t.Logf("%#+v", ops)
   202  			return ops, nil
   203  		})
   204  
   205  	u, err := New(ctx, &Options{
   206  		Store:     store,
   207  		Client:    &http.Client{},
   208  		Factories: []driver.UpdaterFactory{fac},
   209  	})
   210  	if err != nil {
   211  		t.Fatal(err)
   212  	}
   213  	defer func() {
   214  		if err := u.Close(); err != nil {
   215  			t.Error(err)
   216  		}
   217  	}()
   218  
   219  	if err := u.Run(ctx, false); err != nil {
   220  		t.Error(err)
   221  	}
   222  
   223  	t.Run("Unchanged", func(t *testing.T) {
   224  		if err := u.Run(ctx, false); err != nil {
   225  			t.Error(err)
   226  		}
   227  	})
   228  }