Repository: sonatype-nexus-community/nancy
Pull request #133 (2020-04-15 19:15:45)

RIP Badger
The time has come, let's simplify our caching DB

This pull request makes the following changes:
* Moves cache functionality into `/ossindex/internal/cache`
* Adds two protected functions only accessible to us, `func Insert(coordinates []types.Coordinate) (err error)` and `func GetCacheValues(purls []string) ([]string, []types.Coordinate, error)` that accomplish inserting a value into the cache, and then getting (and removing if it doesn't exist)
* Rips out Badger and replaces it with `https://github.com/recoilme/pudge`, which is written entirely with the stdlib, and looks pretty good in use so far
* Sets tests back to run in parallel, since we no longer have a lockfile issue
* Allows for TTL to be configured so that a) easier to test and b) we can allow users to set it later
* Allows for DBName to be configured to that a) easier to test

It relates to the following issue #s:
* Fixes #54 #79 #3 

cc @bhamail / @DarthHater



Patch:
diff --git a/Makefile b/Makefile
index 0e1dd5c..935c0e3 100644
--- a/Makefile
+++ b/Makefile
@@ -40,7 +40,7 @@ build:
 	$(GO_BUILD_FLAGS) $(GOBUILD) -o $(BINARY_NAME) -v
 
 test: build
-	$(GOTEST) -v -count=1 -p=1 ./... 2>&1
+	$(GOTEST) -v ./... 2>&1
 
 integration-test: build
 	cd packages/testdata && ../../$(BINARY_NAME) Gopkg.lock && cd -
diff --git a/go.mod b/go.mod
index 9d2a15c..2210f4a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,6 @@
 module github.com/sonatype-nexus-community/nancy
 
 require (
-	github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 // indirect
 	github.com/Flaque/filet v0.0.0-20190209224823-fc4d33cfcf93
 	github.com/Masterminds/semver v0.0.0-20180403130225-3c92f33da7a8
 	github.com/Masterminds/vcs v1.13.1 // indirect
@@ -9,9 +8,6 @@ require (
 	github.com/beevik/etree v1.1.0
 	github.com/boltdb/bolt v1.3.1 // indirect
 	github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a
-	github.com/dgraph-io/badger v1.5.5-0.20181004181505-439fd464b155
-	github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect
-	github.com/dustin/go-humanize v1.0.0 // indirect
 	github.com/golang/dep v0.5.4
 	github.com/golang/protobuf v1.2.0 // indirect
 	github.com/google/go-cmp v0.4.0 // indirect
@@ -23,12 +19,12 @@ require (
 	github.com/package-url/packageurl-go v0.1.0
 	github.com/pelletier/go-toml v1.4.0 // indirect
 	github.com/pkg/errors v0.8.0 // indirect
+	github.com/recoilme/pudge v1.0.3
 	github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 // indirect
 	github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24
 	github.com/sirupsen/logrus v1.5.0
 	github.com/spf13/afero v1.2.2 // indirect
 	github.com/stretchr/testify v1.3.0
-	golang.org/x/net v0.0.0-20181220203305-927f97764cc3 // indirect
 	golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect
 	golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect
 	gopkg.in/go-playground/assert.v1 v1.2.1
diff --git a/go.sum b/go.sum
index 3699bed..319e5e5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,3 @@
-github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 h1:PqzgE6kAMi81xWQA2QIVxjWkFHptGgC547vchpUbtFo=
-github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Flaque/filet v0.0.0-20190209224823-fc4d33cfcf93 h1:NnAUCP75PRm8yWE7+MZBIAR6PA9iwsBYEc6ZNYOy+AQ=
@@ -19,12 +17,6 @@ github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a/go.mod
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgraph-io/badger v1.5.5-0.20181004181505-439fd464b155 h1:T4boC9W0W8nXwg7MAGjZRWkG+2yiJ56tQU4HjH/UM2g=
-github.com/dgraph-io/badger v1.5.5-0.20181004181505-439fd464b155/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
-github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 h1:afESQBXJEnj3fu+34X//E8Wg3nEbMJxJkwSc0tPePK0=
-github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
-github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/golang/dep v0.5.4 h1:WfV5qbGwsBNUDhk+pfI6emWm7SdDFsnSWkqCMNG3BRs=
 github.com/golang/dep v0.5.4/go.mod h1:6RZ2Wai7dSWk7qL55sDYk+8UPFqcW7all2KDBraPPFA=
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
@@ -51,6 +43,8 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/recoilme/pudge v1.0.3 h1:h/9dEv5fRqtzM4lnO69kUoN+k7ukxxrW9NGb9ug0grM=
+github.com/recoilme/pudge v1.0.3/go.mod h1:VMvxBLVkrSStldckzCsETBXox3pfovfrnEchafXk8qA=
 github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 h1:tnWWLf0nI2TI62Wd/ZOea4XYqE+y1sf2pdm+VItsc0c=
 github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353/go.mod h1:5HStXbIikwtDAgAIqiQIqVgMn7mlvZa6PTpwiAVYGYg=
 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
@@ -63,8 +57,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/ossindex/internal/cache/cache.go b/ossindex/internal/cache/cache.go
new file mode 100644
index 0000000..8257fc8
--- /dev/null
+++ b/ossindex/internal/cache/cache.go
@@ -0,0 +1,172 @@
+//
+// Copyright 2020-present Sonatype Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// Package cache has definitions and functions for processing the OSS Index Feed
+package cache
+
+import (
+	"errors"
+	"os"
+	"os/user"
+	"path"
+	"strings"
+	"time"
+
+	"github.com/recoilme/pudge"
+	"github.com/sonatype-nexus-community/nancy/customerrors"
+	. "github.com/sonatype-nexus-community/nancy/logger"
+	"github.com/sonatype-nexus-community/nancy/types"
+)
+
+// Cache is a struct with methods meant to be used for getting values from a DB cache
+// DBName can be used to override the actual database name, primarily for testing
+// TTL can be used to set the amount of time a specific object can live in the cache
+type Cache struct {
+	DBName string
+	TTL    time.Time
+}
+
+const dbDirName = "nancy"
+
+// DBValue is a local struct used for adding a TTL to a Coordinates struct
+type DBValue struct {
+	Coordinates types.Coordinate
+	TTL         int64
+}
+
+func (c *Cache) getDatabasePath() (dbDir string) {
+	usr, err := user.Current()
+	customerrors.Check(err, "Error getting user home")
+
+	return path.Join(usr.HomeDir, types.OssIndexDirName, dbDirName, c.DBName)
+}
+
+// RemoveCache deletes the cache database
+func (c *Cache) RemoveCache() error {
+	defer func() {
+		if err := pudge.CloseAll(); err != nil {
+			LogLady.WithField("error", err).Error("An error occurred with closing the Pudge DB")
+		}
+	}()
+
+	err := pudge.DeleteFile(c.getDatabasePath())
+	if err == nil {
+		return nil
+	}
+	if _, ok := err.(*os.PathError); ok {
+		LogLady.WithField("error", err).Error("Unable to delete database, looks like it doesn't exist")
+		return nil
+	}
+	err = pudge.BackupAll(c.getDatabasePath())
+	if err != nil {
+		return err
+	}
+
+	return err
+}
+
+// Insert takes a slice of Coordinates, and inserts them into the cache database.
+// An error is returned if there is an issue setting a key into the cache.
+func (c *Cache) Insert(coordinates []types.Coordinate) (err error) {
+	defer func() {
+		if err := pudge.CloseAll(); err != nil {
+			LogLady.WithField("error", err).Error("An error occurred with closing the Pudge DB")
+		}
+	}()
+
+	doSet := func(coordinate types.Coordinate) error {
+		err = pudge.Set(c.getDatabasePath(), strings.ToLower(coordinate.Coordinates), DBValue{Coordinates: coordinate, TTL: c.TTL.Unix()})
+		if err != nil {
+			LogLady.WithField("error", err).Error("Unable to add coordinate to cache DB")
+			return err
+		}
+		return nil
+	}
+
+	for i := 0; i < len(coordinates); i++ {
+		coord := coordinates[i]
+		var exists DBValue
+		err = c.getKeyAndHydrate(coord.Coordinates, &exists)
+		if err != nil {
+			if errors.Is(err, pudge.ErrKeyNotFound) {
+				err = doSet(coordinates[i])
+				if err != nil {
+					return
+				}
+			}
+			continue
+		}
+		if exists.TTL < time.Now().Unix() {
+			c.deleteKey(coord.Coordinates)
+			err = doSet(coord)
+			if err != nil {
+				continue
+			}
+		}
+	}
+	return
+}
+
+// GetCacheValues takes a slice of purls, and checks to see if they are in the cache.
+// It will return a new slice of purls used to talk to OSS Index (if any are not in the cache),
+// a partially hydrated results slice if there are results in the cache, and an error if the world
+// ends, or we wrote bad code, whichever comes first.
+func (c *Cache) GetCacheValues(purls []string) ([]string, []types.Coordinate, error) {
+	defer func() {
+		if err := pudge.CloseAll(); err != nil {
+			LogLady.WithField("error", err).Error("An error occurred with closing the Pudge DB")
+		}
+	}()
+
+	var newPurls []string
+	var results []types.Coordinate
+
+	for i := 0; i < len(purls); i++ {
+		purl := purls[i]
+		var item DBValue
+		err := c.getKeyAndHydrate(purl, &item)
+		if err != nil {
+			if errors.Is(err, pudge.ErrKeyNotFound) {
+				newPurls = append(newPurls, purl)
+				continue
+			} else {
+				return nil, nil, err
+			}
+		}
+
+		if item.TTL < time.Now().Unix() {
+			newPurls = append(newPurls, purl)
+			c.deleteKey(item.Coordinates.Coordinates)
+			continue
+		} else {
+			LogLady.WithField("coordinate", item.Coordinates).Info("Result found in cache, moving forward and hydrating results")
+			results = append(results, item.Coordinates)
+		}
+	}
+
+	return newPurls, results, nil
+}
+
+func (c *Cache) deleteKey(key string) {
+	err := pudge.Delete(c.getDatabasePath(), strings.ToLower(key))
+	if err != nil {
+		LogLady.WithField("error", err).Error("Unable to delete value from pudge db")
+	}
+}
+
+func (c *Cache) getKeyAndHydrate(key string, item *DBValue) error {
+	return pudge.Get(c.getDatabasePath(), strings.ToLower(key), item)
+}
diff --git a/ossindex/ossindex.go b/ossindex/ossindex.go
index 3348a4f..028210c 100644
--- a/ossindex/ossindex.go
+++ b/ossindex/ossindex.go
@@ -20,73 +20,52 @@ package ossindex
 import (
 	"bytes"
 	"encoding/json"
-	"flag"
 	"fmt"
 	"io/ioutil"
 	"net/http"
-	"os"
-	"os/user"
-	"path"
-	"strings"
 	"time"
 
-	"github.com/dgraph-io/badger"
 	"github.com/sonatype-nexus-community/nancy/configuration"
 	"github.com/sonatype-nexus-community/nancy/customerrors"
 	. "github.com/sonatype-nexus-community/nancy/logger"
+	"github.com/sonatype-nexus-community/nancy/ossindex/internal/cache"
 	"github.com/sonatype-nexus-community/nancy/types"
 	"github.com/sonatype-nexus-community/nancy/useragent"
 )
 
-const dbValueDirName = "golang"
-
-const defaultOssIndexUrl = "https://ossindex.sonatype.org/api/v3/component-report"
+const defaultOssIndexURL = "https://ossindex.sonatype.org/api/v3/component-report"
 
+// MAX_COORDS is the maximum amount of coords to query OSS Index with at one time
+//
+// Deprecated: use MaxCoords instead
 const MAX_COORDS = 128
 
+// MaxCoords is the maximum amount of coords to query OSS Index with at one time
+const MaxCoords = MAX_COORDS
+
 var (
-	ossIndexUrl string
+	ossIndexURL string
 )
 
-func getDatabaseDirectory() (dbDir string) {
-	LogLady.Trace("Attempting to get database directory")
-	usr, err := user.Current()
-	customerrors.Check(err, "Error getting user home")
-
-	LogLady.WithField("home_dir", usr.HomeDir).Trace("Obtained user directory")
-	var leftPath = path.Join(usr.HomeDir, types.OssIndexDirName)
-	var fullPath string
-	if flag.Lookup("test") == nil {
-		fullPath = path.Join(leftPath, dbValueDirName)
-	} else {
-		fullPath = path.Join(leftPath, "test-nancy")
-	}
+var dbCache *cache.Cache
 
-	return fullPath
-}
-
-// RemoveCacheDirectory deletes the local database directory.
-func RemoveCacheDirectory() error {
-	return os.RemoveAll(getDatabaseDirectory())
+func init() {
+	dbCache = &cache.Cache{
+		DBName: "nancy-cache",
+		TTL:    time.Now().Local().Add(time.Hour * 12),
+	}
 }
 
-func getOssIndexUrl() string {
-	if ossIndexUrl == "" {
-		ossIndexUrl = defaultOssIndexUrl
+func getOssIndexURL() string {
+	if ossIndexURL == "" {
+		ossIndexURL = defaultOssIndexURL
 	}
-	return ossIndexUrl
+	return ossIndexURL
 }
 
-func openDb(dbDir string) (db *badger.DB, err error) {
-	LogLady.Debug("Attempting to open Badger DB")
-	opts := badger.DefaultOptions
-
-	opts.Dir = getDatabaseDirectory()
-	opts.ValueDir = getDatabaseDirectory()
-	LogLady.WithField("badger_opts", opts).Debug("Set Badger Options")
-
-	db, err = badger.Open(opts)
-	return
+// RemoveCacheDirectory deletes the local database directory.
+func RemoveCacheDirectory() error {
+	return dbCache.RemoveCache()
 }
 
 // AuditPackages will given a list of Package URLs, run an OSS Index audit.
@@ -103,43 +82,10 @@ func AuditPackagesWithOSSIndex(purls []string, config *configuration.Configurati
 }
 
 func doAuditPackages(purls []string, config *configuration.Configuration) ([]types.Coordinate, error) {
-	dbDir := getDatabaseDirectory()
-	if err := os.MkdirAll(dbDir, os.ModePerm); err != nil {
-		return nil, err
-	}
-
-	// Initialize the cache
-	db, err := openDb(dbDir)
+	newPurls, results, err := dbCache.GetCacheValues(purls)
 	customerrors.Check(err, "Error initializing cache")
-	defer db.Close()
-
-	var newPurls []string
-	var results []types.Coordinate
-
-	err = db.View(func(txn *badger.Txn) error {
-		for _, purl := range purls {
-			item, err := txn.Get([]byte(strings.ToLower(purl)))
-			if err == nil {
-				err := item.Value(func(val []byte) error {
-					var coordinate types.Coordinate
-					err := json.Unmarshal(val, &coordinate)
-					results = append(results, coordinate)
-					return err
-				})
-				if err != nil {
-					newPurls = append(newPurls, purl)
-				}
-			} else {
-				newPurls = append(newPurls, purl)
-			}
-		}
-		return nil
-	})
-	if err != nil {
-		return nil, err
-	}
 
-	var chunks = chunk(newPurls, MAX_COORDS)
+	chunks := chunk(newPurls, MaxCoords)
 
 	for _, chunk := range chunks {
 		if len(chunk) > 0 {
@@ -153,26 +99,11 @@ func doAuditPackages(purls []string, config *configuration.Configuration) ([]typ
 				return nil, err
 			}
 
-			LogLady.WithField("coordinates", coordinates).Info("Coordinates unmarshalled from OSS Index")
-
-			// Cache the new results
-			if err := db.Update(func(txn *badger.Txn) error {
-				for i := 0; i < len(coordinates); i++ {
-					coord := coordinates[i].Coordinates
-					results = append(results, coordinates[i])
-
-					coordJSON, _ := json.Marshal(coordinates[i])
-					LogLady.WithField("json", coordinates[i]).Info("Marshall coordinate into json for insertion into DB")
+			results = append(results, coordinates...)
 
-					err := txn.SetWithTTL([]byte(strings.ToLower(coord)), []byte(coordJSON), time.Hour*12)
-					if err != nil {
-						LogLady.WithField("error", err).Error("Unable to add coordinate to cache DB")
-						return err
-					}
-				}
-
-				return nil
-			}); err != nil {
+			LogLady.WithField("coordinates", coordinates).Info("Coordinates unmarshalled from OSS Index")
+			err = dbCache.Insert(coordinates)
+			if err != nil {
 				return nil, err
 			}
 		}
@@ -226,7 +157,7 @@ func setupRequest(jsonStr []byte, config *configuration.Configuration) (req *htt
 	LogLady.WithField("json_string", string(jsonStr)).Debug("Setting up new POST request to OSS Index")
 	req, err = http.NewRequest(
 		"POST",
-		getOssIndexUrl(),
+		getOssIndexURL(),
 		bytes.NewBuffer(jsonStr),
 	)
 	if err != nil {


Test patch:
diff --git a/logger/logger_test.go b/logger/logger_test.go
index ad9703f..54db07e 100644
--- a/logger/logger_test.go
+++ b/logger/logger_test.go
@@ -42,15 +42,18 @@ func TestLogger(t *testing.T) {
 	var logTest LogTest
 
 	err = json.Unmarshal(dat, &logTest)
+
 	if err != nil {
 		t.Error("Improperly written log, should be valid json")
 	}
 
 	if logTest.Level != "info" {
+		t.Error(logTest)
 		t.Error("Log level not set properly")
 	}
 
 	if logTest.Msg != "Test" {
+		t.Error(logTest)
 		t.Error("Message not written to log correctly")
 	}
 }
diff --git a/ossindex/internal/cache/cache_test.go b/ossindex/internal/cache/cache_test.go
new file mode 100644
index 0000000..562c84a
--- /dev/null
+++ b/ossindex/internal/cache/cache_test.go
@@ -0,0 +1,119 @@
+//
+// Copyright 2020-present Sonatype Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// Package cache has definitions and functions for processing the OSS Index Feed
+package cache
+
+import (
+	"testing"
+	"time"
+
+	"github.com/shopspring/decimal"
+	"github.com/sonatype-nexus-community/nancy/types"
+	"github.com/stretchr/testify/assert"
+)
+
+var coordinates []types.Coordinate
+
+var purls []string
+
+func TestInsert(t *testing.T) {
+	cache := setupTestsAndCache(t)
+
+	err := cache.Insert(coordinates)
+	assert.Nil(t, err)
+
+	var result DBValue
+	err = cache.getKeyAndHydrate(coordinates[0].Coordinates, &result)
+
+	assert.Equal(t, coordinates[0], result.Coordinates)
+	assert.Nil(t, err)
+
+	tearDown(t, cache)
+}
+
+func TestGetWithRegularTTL(t *testing.T) {
+	cache := setupTestsAndCache(t)
+
+	err := cache.Insert(coordinates)
+	assert.Nil(t, err)
+
+	newPurls, results, err := cache.GetCacheValues(purls)
+
+	assert.Empty(t, newPurls)
+	assert.Equal(t, results, coordinates)
+	assert.Nil(t, err)
+
+	tearDown(t, cache)
+}
+
+func TestGetWithExpiredTTL(t *testing.T) {
+	cache := setupTestsAndCache(t)
+	cache.TTL = time.Now().AddDate(0, 0, -1)
+
+	err := cache.Insert(coordinates)
+	assert.Nil(t, err)
+
+	newPurls, results, err := cache.GetCacheValues(purls)
+
+	assert.Equal(t, purls, newPurls)
+	assert.Empty(t, results)
+	assert.Nil(t, err)
+
+	var result DBValue
+	err = cache.getKeyAndHydrate(purls[0], &result)
+	assert.NotNil(t, err)
+	assert.Equal(t, err.Error(), "Error: key not found")
+
+	tearDown(t, cache)
+}
+
+func setupTestsAndCache(t *testing.T) *Cache {
+	dec, _ := decimal.NewFromString("9.8")
+	coordinate := types.Coordinate{
+		Coordinates: "pkg:golang/test@0.0.0",
+		Reference:   "http://www.innernet.com",
+		Vulnerabilities: []types.Vulnerability{
+			{
+				Id:          "id",
+				Title:       "test",
+				Description: "description",
+				CvssScore:   dec,
+				CvssVector:  "vectorvictor",
+				Cve:         "CVE-123-123",
+				Reference:   "http://www.internet.com",
+				Excluded:    false,
+			},
+		},
+	}
+
+	purls = append(purls, "pkg:golang/test@0.0.0")
+
+	coordinates = append(coordinates, coordinate)
+	cache := Cache{DBName: "nancy-cache-test", TTL: time.Now().Local().Add(time.Hour * 12)}
+	err := cache.RemoveCache()
+	if err != nil {
+		t.Error(err)
+	}
+	return &cache
+}
+
+func tearDown(t *testing.T, cache *Cache) {
+	err := cache.RemoveCache()
+	if err != nil {
+		t.Error(err)
+	}
+}
diff --git a/ossindex/ossindex_test.go b/ossindex/ossindex_test.go
index 88bd4b8..c158d0b 100644
--- a/ossindex/ossindex_test.go
+++ b/ossindex/ossindex_test.go
@@ -22,12 +22,11 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/url"
-	"os"
 	"strings"
 	"testing"
 	"time"
 
-	"github.com/dgraph-io/badger"
+	"github.com/shopspring/decimal"
 	"github.com/sonatype-nexus-community/nancy/configuration"
 	"github.com/sonatype-nexus-community/nancy/types"
 	"github.com/stretchr/testify/assert"
@@ -36,57 +35,21 @@ import (
 const purl = "pkg:github/BurntSushi/toml@0.3.1"
 
 var lowerCasePurl = strings.ToLower(purl)
-var expectedCoordinate = types.Coordinate{
-	Coordinates:     lowerCasePurl,
-	Reference:       "https://ossindex.sonatype.org/component/" + lowerCasePurl,
-	Vulnerabilities: []types.Vulnerability{},
-}
-
-func exists(filePath string) (exists bool) {
-	exists = true
-
-	if _, err := os.Stat(filePath); os.IsNotExist(err) {
-		exists = false
-	}
-
-	return
-}
-
-func setupTestCaseMoveCacheDb(t *testing.T) func(t *testing.T) {
-	// temporarily move existing cache db
-	cacheValueDir := getDatabaseDirectory() + "/" + dbValueDirName
-	var mustRestoreExistingValueDir = exists(cacheValueDir)
-	backupValueDir := getDatabaseDirectory() + "/" + dbValueDirName + "-NancyOssIndexTestBackup"
-	if mustRestoreExistingValueDir {
-		// move existing valueDir to backup name
-		assert.Nil(t, os.Rename(cacheValueDir, backupValueDir))
-	}
-
-	return func(t *testing.T) {
-		// remove valueDir created during test
-		assert.Nil(t, os.RemoveAll(cacheValueDir))
-
-		if mustRestoreExistingValueDir {
-			// restore existing valueDir from backup name
-			assert.Nil(t, os.Rename(backupValueDir, cacheValueDir))
-		}
-	}
-}
+var expectedCoordinate types.Coordinate
 
 func TestOssIndexUrlDefault(t *testing.T) {
-	ossIndexUrl = ""
-	assert.Equal(t, defaultOssIndexUrl, getOssIndexUrl())
+	setupTest(t)
+	ossIndexURL = ""
+	assert.Equal(t, defaultOssIndexURL, getOssIndexURL())
 }
 
 func TestAuditPackages_Empty(t *testing.T) {
+	setupTest(t)
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		t.Errorf("No call should occur with empty package. called: %v", r)
 	}))
 	defer ts.Close()
-	ossIndexUrl = ts.URL
-
-	teardownTestCase := setupTestCaseMoveCacheDb(t)
-	defer teardownTestCase(t)
+	ossIndexURL = ts.URL
 
 	coordinates, err := AuditPackages([]string{})
 	assert.Equal(t, []types.Coordinate(nil), coordinates)
@@ -94,14 +57,12 @@ func TestAuditPackages_Empty(t *testing.T) {
 }
 
 func TestAuditPackages_Nil(t *testing.T) {
+	setupTest(t)
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		t.Errorf("No call should occur with nil package. called: %v", r)
 	}))
 	defer ts.Close()
-	ossIndexUrl = ts.URL
-
-	teardownTestCase := setupTestCaseMoveCacheDb(t)
-	defer teardownTestCase(t)
+	ossIndexURL = ts.URL
 
 	coordinates, err := AuditPackages(nil)
 	assert.Equal(t, []types.Coordinate(nil), coordinates)
@@ -109,14 +70,12 @@ func TestAuditPackages_Nil(t *testing.T) {
 }
 
 func TestAuditPackages_ErrorHttpRequest(t *testing.T) {
+	setupTest(t)
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		t.Errorf("No call should occur with nil package. called: %v", r)
 	}))
 	defer ts.Close()
-	ossIndexUrl = ts.URL + "\\"
-
-	teardownTestCase := setupTestCaseMoveCacheDb(t)
-	defer teardownTestCase(t)
+	ossIndexURL = ts.URL + "\\"
 
 	coordinates, err := AuditPackages([]string{"nonexistent-purl"})
 	assert.Equal(t, []types.Coordinate(nil), coordinates)
@@ -125,6 +84,7 @@ func TestAuditPackages_ErrorHttpRequest(t *testing.T) {
 }
 
 func TestAuditPackages_ErrorNonExistentPurl(t *testing.T) {
+	setupTest(t)
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		assert.Equal(t, http.MethodPost, r.Method)
 		assert.Equal(t, "/", r.URL.EscapedPath())
@@ -132,10 +92,7 @@ func TestAuditPackages_ErrorNonExistentPurl(t *testing.T) {
 		w.WriteHeader(http.StatusBadRequest)
 	}))
 	defer ts.Close()
-	ossIndexUrl = ts.URL
-
-	teardownTestCase := setupTestCaseMoveCacheDb(t)
-	defer teardownTestCase(t)
+	ossIndexURL = ts.URL
 
 	coordinates, err := AuditPackages([]string{"nonexistent-purl"})
 	assert.Equal(t, []types.Coordinate(nil), coordinates)
@@ -143,6 +100,7 @@ func TestAuditPackages_ErrorNonExistentPurl(t *testing.T) {
 }
 
 func TestAuditPackages_ErrorBadResponseBody(t *testing.T) {
+	setupTest(t)
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		assert.Equal(t, http.MethodPost, r.Method)
 		assert.Equal(t, "/", r.URL.EscapedPath())
@@ -151,10 +109,7 @@ func TestAuditPackages_ErrorBadResponseBody(t *testing.T) {
 		_, _ = w.Write([]byte("badStuff"))
 	}))
 	defer ts.Close()
-	ossIndexUrl = ts.URL
-
-	teardownTestCase := setupTestCaseMoveCacheDb(t)
-	defer teardownTestCase(t)
+	ossIndexURL = ts.URL
 
 	coordinates, err := AuditPackages([]string{purl})
 
@@ -165,14 +120,12 @@ func TestAuditPackages_ErrorBadResponseBody(t *testing.T) {
 }
 
 func TestAuditPackages_NewPackage(t *testing.T) {
+	setupTest(t)
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		verifyClientCallAndWriteValidPackageResponse(t, r, w)
 	}))
 	defer ts.Close()
-	ossIndexUrl = ts.URL
-
-	teardownTestCase := setupTestCaseMoveCacheDb(t)
-	defer teardownTestCase(t)
+	ossIndexURL = ts.URL
 
 	coordinates, err := AuditPackages([]string{purl})
 
@@ -184,39 +137,26 @@ func verifyClientCallAndWriteValidPackageResponse(t *testing.T, r *http.Request,
 	assert.Equal(t, http.MethodPost, r.Method)
 	assert.Equal(t, "/", r.URL.EscapedPath())
 	w.WriteHeader(http.StatusOK)
-	coordinates := []types.Coordinate{
-		{
-			Coordinates:     "pkg:github/burntsushi/toml@0.3.1",
-			Reference:       "https://ossindex.sonatype.org/component/pkg:github/burntsushi/toml@0.3.1",
-			Vulnerabilities: []types.Vulnerability{},
-		},
-	}
+	coordinates := []types.Coordinate{expectedCoordinate}
 	jsonCoordinates, _ := json.Marshal(coordinates)
 	_, _ = w.Write(jsonCoordinates)
 }
 
 func TestAuditPackages_SinglePackage_Cached(t *testing.T) {
+	setupTest(t)
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		t.Errorf("No call should occur with previously cached package. called: %v", r)
 	}))
 	defer ts.Close()
-	ossIndexUrl = ts.URL
+	ossIndexURL = ts.URL
 
-	teardownTestCase := setupTestCaseMoveCacheDb(t)
-	defer teardownTestCase(t)
+	var tempCoordinates []types.Coordinate
+	tempCoordinates = append(tempCoordinates, expectedCoordinate)
 
-	// create the cached package
-	db, err := openDb(getDatabaseDirectory())
-	assert.Nil(t, err)
-	assert.Nil(t, db.Update(func(txn *badger.Txn) error {
-		var coordJson, _ = json.Marshal(expectedCoordinate)
-		err := txn.SetWithTTL([]byte(strings.ToLower(lowerCasePurl)), []byte(coordJson), time.Hour*12)
-		if err != nil {
-			return err
-		}
-		return nil
-	}))
-	assert.Nil(t, db.Close())
+	err := dbCache.Insert(tempCoordinates)
+	if err != nil {
+		t.Error(err)
+	}
 
 	coordinates, err := AuditPackages([]string{purl})
 	assert.Equal(t, []types.Coordinate{expectedCoordinate}, coordinates)
@@ -224,34 +164,55 @@ func TestAuditPackages_SinglePackage_Cached(t *testing.T) {
 }
 
 func TestAuditPackages_SinglePackage_Cached_WithExpiredTTL(t *testing.T) {
+	setupTest(t)
+
+	// Set the cache TTL to a date in the past for testing
+	dbCache.TTL = time.Now().AddDate(0, 0, -1)
+
+	var tempCoordinates []types.Coordinate
+	tempCoordinates = append(tempCoordinates, expectedCoordinate)
+
+	err := dbCache.Insert(tempCoordinates)
+	if err != nil {
+		t.Error(err)
+	}
+
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		verifyClientCallAndWriteValidPackageResponse(t, r, w)
 	}))
 	defer ts.Close()
-	ossIndexUrl = ts.URL
-
-	teardownTestCase := setupTestCaseMoveCacheDb(t)
-	defer teardownTestCase(t)
-
-	// create the cached package with short TTL for the cached item to ensure item TTL expires before we read it
-	db, err := openDb(getDatabaseDirectory())
-	assert.Nil(t, err)
-	assert.Nil(t, db.Update(func(txn *badger.Txn) error {
-		var coordJson, _ = json.Marshal(expectedCoordinate)
-		err := txn.SetWithTTL([]byte(strings.ToLower(lowerCasePurl)), []byte(coordJson), time.Second*1)
-		if err != nil {
-			return err
-		}
-		return nil
-	}))
-	assert.Nil(t, db.Close())
-	time.Sleep(2 * time.Second)
+	ossIndexURL = ts.URL
 
 	coordinates, err := AuditPackages([]string{purl})
 	assert.Equal(t, []types.Coordinate{expectedCoordinate}, coordinates)
 	assert.Nil(t, err)
 }
 
+func setupTest(t *testing.T) {
+	dec, _ := decimal.NewFromString("9.8")
+	expectedCoordinate = types.Coordinate{
+		Coordinates: lowerCasePurl,
+		Reference:   "https://ossindex.sonatype.org/component/" + lowerCasePurl,
+		Vulnerabilities: []types.Vulnerability{
+			{
+				Id:          "id",
+				Title:       "test",
+				Description: "description",
+				CvssScore:   dec,
+				CvssVector:  "vectorvictor",
+				Cve:         "CVE-123-123",
+				Reference:   "http://www.internet.com",
+				Excluded:    false,
+			},
+		},
+	}
+	dbCache.DBName = "nancy-test"
+	err := dbCache.RemoveCache()
+	if err != nil {
+		t.Error(err)
+	}
+}
+
 func TestSetupRequest(t *testing.T) {
 	coordJson, _ := setupJson(t)
 	config := configuration.Configuration{Username: "testuser", Token: "test"}