Repository: prebid/prebid-server
Pull request #1546 (2020-10-16 16:44:33)

Add missing postgres cache init config validation


Patch:
diff --git a/adapters/emx_digital/emx_digital.go b/adapters/emx_digital/emx_digital.go
index ccaa96e6..4bdb25fd 100644
--- a/adapters/emx_digital/emx_digital.go
+++ b/adapters/emx_digital/emx_digital.go
@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"net/url"
 	"strconv"
+	"strings"
 	"time"
 
 	"github.com/mxmCherry/openrtb"
@@ -110,7 +111,6 @@ func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpEmxDigital, error) {
 }
 
 func buildImpBanner(imp *openrtb.Imp) error {
-	imp.Ext = nil
 
 	if imp.Banner == nil {
 		return &errortypes.BadInput{
@@ -137,6 +137,40 @@ func buildImpBanner(imp *openrtb.Imp) error {
 	return nil
 }
 
+func buildImpVideo(imp *openrtb.Imp) error {
+
+	if len(imp.Video.MIMEs) == 0 {
+		return &errortypes.BadInput{
+			Message: fmt.Sprintf("Video: missing required field mimes"),
+		}
+	}
+
+	if imp.Video.H == 0 && imp.Video.W == 0 {
+		return &errortypes.BadInput{
+			Message: fmt.Sprintf("Video: Need at least one size to build request"),
+		}
+	}
+
+	if imp.Video.Protocols != nil {
+		imp.Video.Protocols = cleanProtocol(imp.Video.Protocols)
+	}
+
+	return nil
+}
+
+// not supporting VAST protocol 7 (VAST 4.0);
+func cleanProtocol(protocols []openrtb.Protocol) []openrtb.Protocol {
+	newitems := make([]openrtb.Protocol, 0, len(protocols))
+
+	for _, i := range protocols {
+		if i != openrtb.ProtocolVAST40 {
+			newitems = append(newitems, i)
+		}
+	}
+
+	return newitems
+}
+
 // Add EMX required properties to Imp object
 func addImpProps(imp *openrtb.Imp, secure *int8, emxExt *openrtb_ext.ExtImpEmxDigital) {
 	imp.TagID = emxExt.TagID
@@ -170,14 +204,22 @@ func preprocess(request *openrtb.BidRequest) []error {
 	errors := make([]error, 0, impsCount)
 	resImps := make([]openrtb.Imp, 0, impsCount)
 	secure := int8(0)
-
+	domain := ""
 	if request.Site != nil && request.Site.Page != "" {
-		pageURL, err := url.Parse(request.Site.Page)
-		if err == nil && pageURL.Scheme == "https" {
-			secure = int8(1)
+		domain = request.Site.Page
+	} else if request.App != nil {
+		if request.App.Domain != "" {
+			domain = request.App.Domain
+		} else if request.App.StoreURL != "" {
+			domain = request.App.StoreURL
 		}
 	}
 
+	pageURL, err := url.Parse(domain)
+	if err == nil && pageURL.Scheme == "https" {
+		secure = int8(1)
+	}
+
 	for _, imp := range request.Imp {
 		emxExt, err := unpackImpExt(&imp)
 		if err != nil {
@@ -187,10 +229,17 @@ func preprocess(request *openrtb.BidRequest) []error {
 
 		addImpProps(&imp, &secure, emxExt)
 
-		if err := buildImpBanner(&imp); err != nil {
+		if imp.Video != nil {
+			if err := buildImpVideo(&imp); err != nil {
+				errors = append(errors, err)
+				continue
+			}
+		} else if err := buildImpBanner(&imp); err != nil {
 			errors = append(errors, err)
 			continue
+
 		}
+
 		resImps = append(resImps, imp)
 	}
 
@@ -229,7 +278,7 @@ func (a *EmxDigitalAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern
 
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
 				Bid:     &sb.Bid[i],
-				BidType: "banner",
+				BidType: getBidType(sb.Bid[i].AdM),
 			})
 		}
 	}
@@ -238,6 +287,24 @@ func (a *EmxDigitalAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern
 
 }
 
+func getBidType(bidAdm string) openrtb_ext.BidType {
+	if bidAdm != "" && ContainsAny(bidAdm, []string{"<?xml", "<vast"}) {
+		return openrtb_ext.BidTypeVideo
+	}
+	return openrtb_ext.BidTypeBanner
+}
+
+func ContainsAny(raw string, keys []string) bool {
+	lowerCased := strings.ToLower(raw)
+	for i := 0; i < len(keys); i++ {
+		if strings.Contains(lowerCased, keys[i]) {
+			return true
+		}
+	}
+	return false
+
+}
+
 func NewEmxDigitalBidder(endpoint string) *EmxDigitalAdapter {
 	return &EmxDigitalAdapter{
 		endpoint: endpoint,
diff --git a/config/stored_requests.go b/config/stored_requests.go
index 24956946..cc49e91d 100644
--- a/config/stored_requests.go
+++ b/config/stored_requests.go
@@ -137,9 +137,6 @@ func resolvedStoredRequestsConfig(cfg *Configuration) {
 }
 
 func (cfg *StoredRequests) validate(errs configErrors) configErrors {
-	if cfg.DataType() == AccountDataType && cfg.HTTP.Endpoint != "" {
-		errs = append(errs, fmt.Errorf("%s.http: retrieving accounts via http not available, use accounts.files", cfg.Section()))
-	}
 	if cfg.DataType() == AccountDataType && cfg.Postgres.ConnectionInfo.Database != "" {
 		errs = append(errs, fmt.Errorf("%s.postgres: retrieving accounts via postgres not available, use accounts.files", cfg.Section()))
 	} else {
@@ -185,7 +182,9 @@ func (cfg *PostgresConfig) validate(dataType DataType, errs configErrors) config
 		return errs
 	}
 
-	return cfg.PollUpdates.validate(dataType, errs)
+	errs = cfg.CacheInitialization.validate(dataType, errs)
+	errs = cfg.PollUpdates.validate(dataType, errs)
+	return errs
 }
 
 // PostgresConnection has options which put types to the Postgres Connection string. See:
diff --git a/exchange/auction.go b/exchange/auction.go
index 785eae29..aa36feb2 100644
--- a/exchange/auction.go
+++ b/exchange/auction.go
@@ -93,7 +93,7 @@ func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout in
 	return nil
 }
 
-func newAuction(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, numImps int) *auction {
+func newAuction(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, numImps int, preferDeals bool) *auction {
 	winningBids := make(map[string]*pbsOrtbBid, numImps)
 	winningBidsByBidder := make(map[string]map[openrtb_ext.BidderName]*pbsOrtbBid, numImps)
 
@@ -102,7 +102,7 @@ func newAuction(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, numImps int
 			for _, bid := range seatBid.bids {
 				cpm := bid.bid.Price
 				wbid, ok := winningBids[bid.bid.ImpID]
-				if !ok || cpm > wbid.bid.Price {
+				if !ok || isNewWinningBid(bid.bid, wbid.bid, preferDeals) {
 					winningBids[bid.bid.ImpID] = bid
 				}
 				if bidMap, ok := winningBidsByBidder[bid.bid.ImpID]; ok {
@@ -124,6 +124,19 @@ func newAuction(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, numImps int
 	}
 }
 
+// isNewWinningBid calculates if the new bid (nbid) will win against the current winning bid (wbid) given preferDeals.
+func isNewWinningBid(bid, wbid *openrtb.Bid, preferDeals bool) bool {
+	if preferDeals {
+		if len(wbid.DealID) > 0 && len(bid.DealID) == 0 {
+			return false
+		}
+		if len(wbid.DealID) == 0 && len(bid.DealID) > 0 {
+			return true
+		}
+	}
+	return bid.Price > wbid.Price
+}
+
 func (a *auction) setRoundedPrices(priceGranularity openrtb_ext.PriceGranularity) {
 	roundedPrices := make(map[*pbsOrtbBid]string, 5*len(a.winningBids))
 	for _, topBidsPerImp := range a.winningBidsByBidder {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index c2683b95..5c424769 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -156,9 +156,9 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 			}
 		}
 
-		auc = newAuction(adapterBids, len(bidRequest.Imp))
-
 		if targData != nil {
+			// A non-nil auction is only needed if targeting is active. (It is used below this block to extract cache keys)
+			auc = newAuction(adapterBids, len(bidRequest.Imp), targData.preferDeals)
 			auc.setRoundedPrices(targData.priceGranularity)
 
 			if requestExt.Prebid.SupportDeals {
diff --git a/exchange/targeting.go b/exchange/targeting.go
index 47bfeb65..87bec6f3 100644
--- a/exchange/targeting.go
+++ b/exchange/targeting.go
@@ -23,6 +23,7 @@ type targetData struct {
 	includeCacheBids  bool
 	includeCacheVast  bool
 	includeFormat     bool
+	preferDeals       bool
 	// cacheHost and cachePath exist to supply cache host and path as targeting parameters
 	cacheHost string
 	cachePath string
diff --git a/exchange/utils.go b/exchange/utils.go
index c011be52..63e56e84 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -543,6 +543,7 @@ func getExtTargetData(requestExt *openrtb_ext.ExtRequest, cacheInstructions *ext
 			includeCacheBids:  cacheInstructions.cacheBids,
 			includeCacheVast:  cacheInstructions.cacheVAST,
 			includeFormat:     requestExt.Prebid.Targeting.IncludeFormat,
+			preferDeals:       requestExt.Prebid.Targeting.PreferDeals,
 		}
 	}
 	return targData
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index 79a76c6f..1199edc7 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -102,6 +102,7 @@ type ExtRequestTargeting struct {
 	IncludeBrandCategory *ExtIncludeBrandCategory `json:"includebrandcategory"`
 	IncludeFormat        bool                     `json:"includeformat"`
 	DurationRangeSec     []int                    `json:"durationrangesec"`
+	PreferDeals          bool                     `json:"preferdeals"`
 	AppendBidderNames    bool                     `json:"appendbiddernames,omitempty"`
 }
 
diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go
index 6a36f9e7..903b17bf 100644
--- a/pbsmetrics/config/metrics.go
+++ b/pbsmetrics/config/metrics.go
@@ -104,6 +104,20 @@ func (me *MultiMetricsEngine) RecordRequestTime(labels pbsmetrics.Labels, length
 	}
 }
 
+// RecordStoredDataFetchTime across all engines
+func (me *MultiMetricsEngine) RecordStoredDataFetchTime(labels pbsmetrics.StoredDataLabels, length time.Duration) {
+	for _, thisME := range *me {
+		thisME.RecordStoredDataFetchTime(labels, length)
+	}
+}
+
+// RecordStoredDataError across all engines
+func (me *MultiMetricsEngine) RecordStoredDataError(labels pbsmetrics.StoredDataLabels) {
+	for _, thisME := range *me {
+		thisME.RecordStoredDataError(labels)
+	}
+}
+
 // RecordAdapterPanic across all engines
 func (me *MultiMetricsEngine) RecordAdapterPanic(labels pbsmetrics.AdapterLabels) {
 	for _, thisME := range *me {
@@ -244,6 +258,14 @@ func (me *DummyMetricsEngine) RecordLegacyImps(labels pbsmetrics.Labels, numImps
 func (me *DummyMetricsEngine) RecordRequestTime(labels pbsmetrics.Labels, length time.Duration) {
 }
 
+// RecordStoredDataFetchTime as a noop
+func (me *DummyMetricsEngine) RecordStoredDataFetchTime(labels pbsmetrics.StoredDataLabels, length time.Duration) {
+}
+
+// RecordStoredDataError as a noop
+func (me *DummyMetricsEngine) RecordStoredDataError(labels pbsmetrics.StoredDataLabels) {
+}
+
 // RecordAdapterPanic as a noop
 func (me *DummyMetricsEngine) RecordAdapterPanic(labels pbsmetrics.AdapterLabels) {
 }
diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go
index 26f6ce07..3842bfd2 100644
--- a/pbsmetrics/go_metrics.go
+++ b/pbsmetrics/go_metrics.go
@@ -27,6 +27,8 @@ type Metrics struct {
 	RequestsQueueTimer             map[RequestType]map[bool]metrics.Timer
 	PrebidCacheRequestTimerSuccess metrics.Timer
 	PrebidCacheRequestTimerError   metrics.Timer
+	StoredDataFetchTimer           map[StoredDataType]map[StoredDataFetchType]metrics.Timer
+	StoredDataErrorMeter           map[StoredDataType]map[StoredDataError]metrics.Meter
 	StoredReqCacheMeter            map[CacheResult]metrics.Meter
 	StoredImpCacheMeter            map[CacheResult]metrics.Meter
 	DNSLookupTimer                 metrics.Timer
@@ -131,6 +133,8 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 		RequestsQueueTimer:             make(map[RequestType]map[bool]metrics.Timer),
 		PrebidCacheRequestTimerSuccess: blankTimer,
 		PrebidCacheRequestTimerError:   blankTimer,
+		StoredDataFetchTimer:           make(map[StoredDataType]map[StoredDataFetchType]metrics.Timer),
+		StoredDataErrorMeter:           make(map[StoredDataType]map[StoredDataError]metrics.Meter),
 		StoredReqCacheMeter:            make(map[CacheResult]metrics.Meter),
 		StoredImpCacheMeter:            make(map[CacheResult]metrics.Meter),
 		AmpNoCookieMeter:               blankMeter,
@@ -183,6 +187,17 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 		newMetrics.PrivacyTCFRequestVersion[v] = blankMeter
 	}
 
+	for _, dt := range StoredDataTypes() {
+		newMetrics.StoredDataFetchTimer[dt] = make(map[StoredDataFetchType]metrics.Timer)
+		newMetrics.StoredDataErrorMeter[dt] = make(map[StoredDataError]metrics.Meter)
+		for _, ft := range StoredDataFetchTypes() {
+			newMetrics.StoredDataFetchTimer[dt][ft] = blankTimer
+		}
+		for _, e := range StoredDataErrors() {
+			newMetrics.StoredDataErrorMeter[dt][e] = blankMeter
+		}
+	}
+
 	//to minimize memory usage, queuedTimeout metric is now supported for video endpoint only
 	//boolean value represents 2 general request statuses: accepted and rejected
 	newMetrics.RequestsQueueTimer["video"] = make(map[bool]metrics.Timer)
@@ -218,6 +233,17 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
 	newMetrics.PrebidCacheRequestTimerSuccess = metrics.GetOrRegisterTimer("prebid_cache_request_time.ok", registry)
 	newMetrics.PrebidCacheRequestTimerError = metrics.GetOrRegisterTimer("prebid_cache_request_time.err", registry)
 
+	for _, dt := range StoredDataTypes() {
+		for _, ft := range StoredDataFetchTypes() {
+			timerName := fmt.Sprintf("stored_%s_fetch_time.%s", string(dt), string(ft))
+			newMetrics.StoredDataFetchTimer[dt][ft] = metrics.GetOrRegisterTimer(timerName, registry)
+		}
+		for _, e := range StoredDataErrors() {
+			meterName := fmt.Sprintf("stored_%s_error.%s", string(dt), string(e))
+			newMetrics.StoredDataErrorMeter[dt][e] = metrics.GetOrRegisterMeter(meterName, registry)
+		}
+	}
+
 	newMetrics.AmpNoCookieMeter = metrics.GetOrRegisterMeter("amp_no_cookie_requests", registry)
 	newMetrics.CookieSyncMeter = metrics.GetOrRegisterMeter("cookie_sync_requests", registry)
 	newMetrics.userSyncBadRequest = metrics.GetOrRegisterMeter("usersync.bad_requests", registry)
@@ -444,6 +470,16 @@ func (me *Metrics) RecordRequestTime(labels Labels, length time.Duration) {
 	}
 }
 
+// RecordStoredDataFetchTime implements a part of the MetricsEngine interface
+func (me *Metrics) RecordStoredDataFetchTime(labels StoredDataLabels, length time.Duration) {
+	me.StoredDataFetchTimer[labels.DataType][labels.DataFetchType].Update(length)
+}
+
+// RecordStoredDataError implements a part of the MetricsEngine interface
+func (me *Metrics) RecordStoredDataError(labels StoredDataLabels) {
+	me.StoredDataErrorMeter[labels.DataType][labels.Error].Mark(1)
+}
+
 // RecordAdapterPanic implements a part of the MetricsEngine interface
 func (me *Metrics) RecordAdapterPanic(labels AdapterLabels) {
 	am, ok := me.AdapterMetrics[labels.Adapter]
diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go
index 8133bc73..7f9d66b5 100644
--- a/pbsmetrics/metrics.go
+++ b/pbsmetrics/metrics.go
@@ -51,6 +51,60 @@ type PrivacyLabels struct {
 	LMTEnforced    bool
 }
 
+type StoredDataType string
+
+const (
+	AccountDataType  StoredDataType = "account"
+	AMPDataType      StoredDataType = "amp"
+	CategoryDataType StoredDataType = "category"
+	RequestDataType  StoredDataType = "request"
+	VideoDataType    StoredDataType = "video"
+)
+
+func StoredDataTypes() []StoredDataType {
+	return []StoredDataType{
+		AccountDataType,
+		AMPDataType,
+		CategoryDataType,
+		RequestDataType,
+		VideoDataType,
+	}
+}
+
+type StoredDataFetchType string
+
+const (
+	FetchAll   StoredDataFetchType = "all"
+	FetchDelta StoredDataFetchType = "delta"
+)
+
+func StoredDataFetchTypes() []StoredDataFetchType {
+	return []StoredDataFetchType{
+		FetchAll,
+		FetchDelta,
+	}
+}
+
+type StoredDataLabels struct {
+	DataType      StoredDataType
+	DataFetchType StoredDataFetchType
+	Error         StoredDataError
+}
+
+type StoredDataError string
+
+const (
+	StoredDataErrorNetwork   StoredDataError = "network"
+	StoredDataErrorUndefined StoredDataError = "undefined"
+)
+
+func StoredDataErrors() []StoredDataError {
+	return []StoredDataError{
+		StoredDataErrorNetwork,
+		StoredDataErrorUndefined,
+	}
+}
+
 // Label typecasting. Se below the type definitions for possible values
 
 // DemandSource : Demand source enumeration
@@ -314,6 +368,8 @@ type MetricsEngine interface {
 	RecordUserIDSet(userLabels UserLabels) // Function should verify bidder values
 	RecordStoredReqCacheResult(cacheResult CacheResult, inc int)
 	RecordStoredImpCacheResult(cacheResult CacheResult, inc int)
+	RecordStoredDataFetchTime(labels StoredDataLabels, length time.Duration)
+	RecordStoredDataError(labels StoredDataLabels)
 	RecordPrebidCacheRequestTime(success bool, length time.Duration)
 	RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
 	RecordTimeoutNotice(sucess bool)
diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go
index 42a2d1b4..63b05196 100644
--- a/pbsmetrics/metrics_mock.go
+++ b/pbsmetrics/metrics_mock.go
@@ -42,6 +42,16 @@ func (me *MetricsEngineMock) RecordRequestTime(labels Labels, length time.Durati
 	me.Called(labels, length)
 }
 
+// RecordStoredDataFetchTime mock
+func (me *MetricsEngineMock) RecordStoredDataFetchTime(labels StoredDataLabels, length time.Duration) {
+	me.Called(labels, length)
+}
+
+// RecordStoredDataError mock
+func (me *MetricsEngineMock) RecordStoredDataError(labels StoredDataLabels) {
+	me.Called(labels)
+}
+
 // RecordAdapterPanic mock
 func (me *MetricsEngineMock) RecordAdapterPanic(labels AdapterLabels) {
 	me.Called(labels)
diff --git a/pbsmetrics/prometheus/preload.go b/pbsmetrics/prometheus/preload.go
index 4f62a18a..52620bc6 100644
--- a/pbsmetrics/prometheus/preload.go
+++ b/pbsmetrics/prometheus/preload.go
@@ -7,17 +7,19 @@ import (
 
 func preloadLabelValues(m *Metrics) {
 	var (
-		actionValues          = actionsAsString()
-		adapterErrorValues    = adapterErrorsAsString()
-		adapterValues         = adaptersAsString()
-		bidTypeValues         = []string{markupDeliveryAdm, markupDeliveryNurl}
-		boolValues            = boolValuesAsString()
-		cacheResultValues     = cacheResultsAsString()
-		connectionErrorValues = []string{connectionAcceptError, connectionCloseError}
-		cookieValues          = cookieTypesAsString()
-		requestStatusValues   = requestStatusesAsString()
-		requestTypeValues     = requestTypesAsString()
-		sourceValues          = []string{sourceRequest}
+		actionValues              = actionsAsString()
+		adapterErrorValues        = adapterErrorsAsString()
+		adapterValues             = adaptersAsString()
+		bidTypeValues             = []string{markupDeliveryAdm, markupDeliveryNurl}
+		boolValues                = boolValuesAsString()
+		cacheResultValues         = cacheResultsAsString()
+		connectionErrorValues     = []string{connectionAcceptError, connectionCloseError}
+		cookieValues              = cookieTypesAsString()
+		requestStatusValues       = requestStatusesAsString()
+		requestTypeValues         = requestTypesAsString()
+		storedDataFetchTypeValues = storedDataFetchTypesAsString()
+		storedDataErrorValues     = storedDataErrorsAsString()
+		sourceValues              = []string{sourceRequest}
 	)
 
 	preloadLabelValuesForCounter(m.connectionsError, map[string][]string{
@@ -44,6 +46,46 @@ func preloadLabelValues(m *Metrics) {
 		requestTypeLabel: requestTypeValues,
 	})
 
+	preloadLabelValuesForHistogram(m.storedAccountFetchTimer, map[string][]string{
+		storedDataFetchTypeLabel: storedDataFetchTypeValues,
+	})
+
+	preloadLabelValuesForHistogram(m.storedAMPFetchTimer, map[string][]string{
+		storedDataFetchTypeLabel: storedDataFetchTypeValues,
+	})
+
+	preloadLabelValuesForHistogram(m.storedCategoryFetchTimer, map[string][]string{
+		storedDataFetchTypeLabel: storedDataFetchTypeValues,
+	})
+
+	preloadLabelValuesForHistogram(m.storedRequestFetchTimer, map[string][]string{
+		storedDataFetchTypeLabel: storedDataFetchTypeValues,
+	})
+
+	preloadLabelValuesForHistogram(m.storedVideoFetchTimer, map[string][]string{
+		storedDataFetchTypeLabel: storedDataFetchTypeValues,
+	})
+
+	preloadLabelValuesForCounter(m.storedAccountErrors, map[string][]string{
+		storedDataErrorLabel: storedDataErrorValues,
+	})
+
+	preloadLabelValuesForCounter(m.storedAMPErrors, map[string][]string{
+		storedDataErrorLabel: storedDataErrorValues,
+	})
+
+	preloadLabelValuesForCounter(m.storedCategoryErrors, map[string][]string{
+		storedDataErrorLabel: storedDataErrorValues,
+	})
+
+	preloadLabelValuesForCounter(m.storedRequestErrors, map[string][]string{
+		storedDataErrorLabel: storedDataErrorValues,
+	})
+
+	preloadLabelValuesForCounter(m.storedVideoErrors, map[string][]string{
+		storedDataErrorLabel: storedDataErrorValues,
+	})
+
 	preloadLabelValuesForCounter(m.requestsWithoutCookie, map[string][]string{
 		requestTypeLabel: requestTypeValues,
 	})
diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go
index b42399b2..046d0769 100644
--- a/pbsmetrics/prometheus/prometheus.go
+++ b/pbsmetrics/prometheus/prometheus.go
@@ -28,6 +28,16 @@ type Metrics struct {
 	requestsWithoutCookie        *prometheus.CounterVec
 	storedImpressionsCacheResult *prometheus.CounterVec
 	storedRequestCacheResult     *prometheus.CounterVec
+	storedAccountFetchTimer      *prometheus.HistogramVec
+	storedAccountErrors          *prometheus.CounterVec
+	storedAMPFetchTimer          *prometheus.HistogramVec
+	storedAMPErrors              *prometheus.CounterVec
+	storedCategoryFetchTimer     *prometheus.HistogramVec
+	storedCategoryErrors         *prometheus.CounterVec
+	storedRequestFetchTimer      *prometheus.HistogramVec
+	storedRequestErrors          *prometheus.CounterVec
+	storedVideoFetchTimer        *prometheus.HistogramVec
+	storedVideoErrors            *prometheus.CounterVec
 	timeoutNotifications         *prometheus.CounterVec
 	dnsLookupTimer               prometheus.Histogram
 	privacyCCPA                  *prometheus.CounterVec
@@ -102,6 +112,11 @@ const (
 	sourceRequest = "request"
 )
 
+const (
+	storedDataFetchTypeLabel = "stored_data_fetch_type"
+	storedDataErrorLabel     = "stored_data_error"
+)
+
 // NewMetrics initializes a new Prometheus metrics instance with preloaded label values.
 func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMetrics) *Metrics {
 	standardTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1}
@@ -171,6 +186,61 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet
 		"Count of stored request cache requests attempts by hits or miss.",
 		[]string{cacheResultLabel})
 
+	metrics.storedAccountFetchTimer = newHistogramVec(cfg, metrics.Registry,
+		"stored_account_fetch_time_seconds",
+		"Seconds to fetch stored accounts labeled by fetch type",
+		[]string{storedDataFetchTypeLabel},
+		standardTimeBuckets)
+
+	metrics.storedAccountErrors = newCounter(cfg, metrics.Registry,
+		"stored_account_errors",
+		"Count of stored account errors by error type",
+		[]string{storedDataErrorLabel})
+
+	metrics.storedAMPFetchTimer = newHistogramVec(cfg, metrics.Registry,
+		"stored_amp_fetch_time_seconds",
+		"Seconds to fetch stored AMP requests labeled by fetch type",
+		[]string{storedDataFetchTypeLabel},
+		standardTimeBuckets)
+
+	metrics.storedAMPErrors = newCounter(cfg, metrics.Registry,
+		"stored_amp_errors",
+		"Count of stored AMP errors by error type",
+		[]string{storedDataErrorLabel})
+
+	metrics.storedCategoryFetchTimer = newHistogramVec(cfg, metrics.Registry,
+		"stored_category_fetch_time_seconds",
+		"Seconds to fetch stored categories labeled by fetch type",
+		[]string{storedDataFetchTypeLabel},
+		standardTimeBuckets)
+
+	metrics.storedCategoryErrors = newCounter(cfg, metrics.Registry,
+		"stored_category_errors",
+		"Count of stored category errors by error type",
+		[]string{storedDataErrorLabel})
+
+	metrics.storedRequestFetchTimer = newHistogramVec(cfg, metrics.Registry,
+		"stored_request_fetch_time_seconds",
+		"Seconds to fetch stored requests labeled by fetch type",
+		[]string{storedDataFetchTypeLabel},
+		standardTimeBuckets)
+
+	metrics.storedRequestErrors = newCounter(cfg, metrics.Registry,
+		"stored_request_errors",
+		"Count of stored request errors by error type",
+		[]string{storedDataErrorLabel})
+
+	metrics.storedVideoFetchTimer = newHistogramVec(cfg, metrics.Registry,
+		"stored_video_fetch_time_seconds",
+		"Seconds to fetch stored video labeled by fetch type",
+		[]string{storedDataFetchTypeLabel},
+		standardTimeBuckets)
+
+	metrics.storedVideoErrors = newCounter(cfg, metrics.Registry,
+		"stored_video_errors",
+		"Count of stored video errors by error type",
+		[]string{storedDataErrorLabel})
+
 	metrics.timeoutNotifications = newCounter(cfg, metrics.Registry,
 		"timeout_notification",
 		"Count of timeout notifications triggered, and if they were successfully sent.",
@@ -387,6 +457,56 @@ func (m *Metrics) RecordRequestTime(labels pbsmetrics.Labels, length time.Durati
 	}
 }
 
+func (m *Metrics) RecordStoredDataFetchTime(labels pbsmetrics.StoredDataLabels, length time.Duration) {
+	switch labels.DataType {
+	case pbsmetrics.AccountDataType:
+		m.storedAccountFetchTimer.With(prometheus.Labels{
+			storedDataFetchTypeLabel: string(labels.DataFetchType),
+		}).Observe(length.Seconds())
+	case pbsmetrics.AMPDataType:
+		m.storedAMPFetchTimer.With(prometheus.Labels{
+			storedDataFetchTypeLabel: string(labels.DataFetchType),
+		}).Observe(length.Seconds())
+	case pbsmetrics.CategoryDataType:
+		m.storedCategoryFetchTimer.With(prometheus.Labels{
+			storedDataFetchTypeLabel: string(labels.DataFetchType),
+		}).Observe(length.Seconds())
+	case pbsmetrics.RequestDataType:
+		m.storedRequestFetchTimer.With(prometheus.Labels{
+			storedDataFetchTypeLabel: string(labels.DataFetchType),
+		}).Observe(length.Seconds())
+	case pbsmetrics.VideoDataType:
+		m.storedVideoFetchTimer.With(prometheus.Labels{
+			storedDataFetchTypeLabel: string(labels.DataFetchType),
+		}).Observe(length.Seconds())
+	}
+}
+
+func (m *Metrics) RecordStoredDataError(labels pbsmetrics.StoredDataLabels) {
+	switch labels.DataType {
+	case pbsmetrics.AccountDataType:
+		m.storedAccountErrors.With(prometheus.Labels{
+			storedDataErrorLabel: string(labels.Error),
+		}).Inc()
+	case pbsmetrics.AMPDataType:
+		m.storedAMPErrors.With(prometheus.Labels{
+			storedDataErrorLabel: string(labels.Error),
+		}).Inc()
+	case pbsmetrics.CategoryDataType:
+		m.storedCategoryErrors.With(prometheus.Labels{
+			storedDataErrorLabel: string(labels.Error),
+		}).Inc()
+	case pbsmetrics.RequestDataType:
+		m.storedRequestErrors.With(prometheus.Labels{
+			storedDataErrorLabel: string(labels.Error),
+		}).Inc()
+	case pbsmetrics.VideoDataType:
+		m.storedVideoErrors.With(prometheus.Labels{
+			storedDataErrorLabel: string(labels.Error),
+		}).Inc()
+	}
+}
+
 func (m *Metrics) RecordAdapterRequest(labels pbsmetrics.AdapterLabels) {
 	m.adapterRequests.With(prometheus.Labels{
 		adapterLabel: string(labels.Adapter),
diff --git a/pbsmetrics/prometheus/type_conversion.go b/pbsmetrics/prometheus/type_conversion.go
index 55a7092e..75efb377 100644
--- a/pbsmetrics/prometheus/type_conversion.go
+++ b/pbsmetrics/prometheus/type_conversion.go
@@ -77,6 +77,33 @@ func requestTypesAsString() []string {
 	return valuesAsString
 }
 
+func storedDataTypesAsString() []string {
+	values := pbsmetrics.StoredDataTypes()
+	valuesAsString := make([]string, len(values))
+	for i, v := range values {
+		valuesAsString[i] = string(v)
+	}
+	return valuesAsString
+}
+
+func storedDataFetchTypesAsString() []string {
+	values := pbsmetrics.StoredDataFetchTypes()
+	valuesAsString := make([]string, len(values))
+	for i, v := range values {
+		valuesAsString[i] = string(v)
+	}
+	return valuesAsString
+}
+
+func storedDataErrorsAsString() []string {
+	values := pbsmetrics.StoredDataErrors()
+	valuesAsString := make([]string, len(values))
+	for i, v := range values {
+		valuesAsString[i] = string(v)
+	}
+	return valuesAsString
+}
+
 func tcfVersionsAsString() []string {
 	values := pbsmetrics.TCFVersions()
 	valuesAsString := make([]string, len(values))
diff --git a/static/bidder-info/emx_digital.yaml b/static/bidder-info/emx_digital.yaml
index 40f73fd0..49a06809 100644
--- a/static/bidder-info/emx_digital.yaml
+++ b/static/bidder-info/emx_digital.yaml
@@ -4,3 +4,8 @@ capabilities:
   site:
     mediaTypes:
       - banner
+      - video
+  app:
+    mediaTypes:
+      - banner
+      - video
\ No newline at end of file
diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go
index d533d531..bc12caec 100644
--- a/stored_requests/backends/http_fetcher/fetcher.go
+++ b/stored_requests/backends/http_fetcher/fetcher.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"net/url"
 	"strings"
 
 	"github.com/prebid/prebid-server/stored_requests"
@@ -19,9 +20,13 @@ import (
 //
 // This file expects the endpoint to satisfy the following API:
 //
+// Stored requests
 // GET {endpoint}?request-ids=["req1","req2"]&imp-ids=["imp1","imp2","imp3"]
 //
-// This endpoint should return a payload like:
+// Accounts
+// GET {endpoint}?account-ids=["acc1","acc2"]
+//
+// The above endpoints should return a payload like:
 //
 // {
 //   "requests": {
@@ -34,12 +39,25 @@ import (
 //     "imp3": null // If imp3 is not found
 //   }
 // }
+// or
+// {
+//   "accounts": {
+//     "acc1": { ... config data for acc1 ... },
+//     "acc2": { ... config data for acc2 ... },
+//   },
+// }
 //
 //
 func NewFetcher(client *http.Client, endpoint string) *HttpFetcher {
 	// Do some work up-front to figure out if the (configurable) endpoint has a query string or not.
 	// When we build requests, we'll either want to add `?request-ids=...&imp-ids=...` _or_
-	// `&request-ids=...&imp-ids=...`, depending.
+	// `&request-ids=...&imp-ids=...`.
+
+	if _, err := url.Parse(endpoint); err != nil {
+		glog.Fatalf(`Invalid endpoint "%s": %v`, endpoint, err)
+	}
+	glog.Infof("Making http_fetcher for endpoint %v", endpoint)
+
 	urlPrefix := endpoint
 	if strings.Contains(endpoint, "?") {
 		urlPrefix = urlPrefix + "&"
@@ -47,8 +65,6 @@ func NewFetcher(client *http.Client, endpoint string) *HttpFetcher {
 		urlPrefix = urlPrefix + "?"
 	}
 
-	glog.Info("Making http_fetcher which calls GET " + urlPrefix + "request-ids=%REQUEST_ID_LIST%&imp-ids=%IMP_ID_LIST%")
-
 	return &HttpFetcher{
 		client:   client,
 		Endpoint: urlPrefix,
@@ -81,8 +97,68 @@ func (fetcher *HttpFetcher) FetchRequests(ctx context.Context, requestIDs []stri
 	return
 }
 
-func (fetcher *HttpFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
-	return nil, []error{stored_requests.NotFoundError{accountID, "Account"}}
+// FetchAccounts retrieves account configurations
+//
+// Request format is similar to the one for requests:
+// GET {endpoint}?account-ids=["account1","account2",...]
+//
+// The endpoint is expected to respond with a JSON map with accountID -> json.RawMessage
+// {
+//   "account1": { ... account json ... }
+// }
+// The JSON contents of account config is returned as-is (NOT validated)
+func (fetcher *HttpFetcher) FetchAccounts(ctx context.Context, accountIDs []string) (map[string]json.RawMessage, []error) {
+	if len(accountIDs) == 0 {
+		return nil, nil
+	}
+	httpReq, err := http.NewRequestWithContext(ctx, "GET", fetcher.Endpoint+"account-ids=[\""+strings.Join(accountIDs, "\",\"")+"\"]", nil)
+	if err != nil {
+		return nil, []error{
+			fmt.Errorf(`Error fetching accounts %v via http: build request failed with %v`, accountIDs, err),
+		}
+	}
+	httpResp, err := ctxhttp.Do(ctx, fetcher.client, httpReq)
+	if err != nil {
+		return nil, []error{
+			fmt.Errorf(`Error fetching accounts %v via http: %v`, accountIDs, err),
+		}
+	}
+	defer httpResp.Body.Close()
+	respBytes, err := ioutil.ReadAll(httpResp.Body)
+	if err != nil {
+		return nil, []error{
+			fmt.Errorf(`Error fetching accounts %v via http: error reading response: %v`, accountIDs, err),
+		}
+	}
+	if httpResp.StatusCode != http.StatusOK {
+		return nil, []error{
+			fmt.Errorf(`Error fetching accounts %v via http: unexpected response status %d`, accountIDs, httpResp.StatusCode),
+		}
+	}
+	var responseData accountsResponseContract
+	if err = json.Unmarshal(respBytes, &responseData); err != nil {
+		return nil, []error{
+			fmt.Errorf(`Error fetching accounts %v via http: failed to parse response: %v`, accountIDs, err),
+		}
+	}
+	errs := convertNullsToErrs(responseData.Accounts, "Account", []error{})
+	return responseData.Accounts, errs
+}
+
+// FetchAccount fetchers a single accountID and returns its corresponding json
+func (fetcher *HttpFetcher) FetchAccount(ctx context.Context, accountID string) (accountJSON json.RawMessage, errs []error) {
+	accountData, errs := fetcher.FetchAccounts(ctx, []string{accountID})
+	if len(errs) > 0 {
+		return nil, errs
+	}
+	accountJSON, ok := accountData[accountID]
+	if !ok {
+		return nil, []error{stored_requests.NotFoundError{
+			ID:       accountID,
+			DataType: "Account",
+		}}
+	}
+	return accountJSON, nil
 }
 
 func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
@@ -190,3 +266,7 @@ type responseContract struct {
 	Requests map[string]json.RawMessage `json:"requests"`
 	Imps     map[string]json.RawMessage `json:"imps"`
 }
+
+type accountsResponseContract struct {
+	Accounts map[string]json.RawMessage `json:"accounts"`
+}
diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go
index d32773a7..1036cf0a 100644
--- a/stored_requests/config/config.go
+++ b/stored_requests/config/config.go
@@ -65,7 +65,7 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine pbsmetrics.M
 		}
 	}
 
-	eventProducers := newEventProducers(cfg, client, dbc.db, router)
+	eventProducers := newEventProducers(cfg, client, dbc.db, metricsEngine, router)
 	fetcher = newFetcher(cfg, client, dbc.db)
 
 	var shutdown1 func()
@@ -190,7 +190,7 @@ func newCache(cfg *config.StoredRequests) stored_requests.Cache {
 	return cache
 }
 
-func newEventProducers(cfg *config.StoredRequests, client *http.Client, db *sql.DB, router *httprouter.Router) (eventProducers []events.EventProducer) {
+func newEventProducers(cfg *config.StoredRequests, client *http.Client, db *sql.DB, metricsEngine pbsmetrics.MetricsEngine, router *httprouter.Router) (eventProducers []events.EventProducer) {
 	if cfg.CacheEvents.Enabled {
 		eventProducers = append(eventProducers, newEventsAPI(router, cfg.CacheEvents.Endpoint))
 	}
@@ -205,6 +205,7 @@ func newEventProducers(cfg *config.StoredRequests, client *http.Client, db *sql.
 			CacheInitTimeout:   time.Duration(cfg.Postgres.CacheInitialization.Timeout) * time.Millisecond,
 			CacheUpdateQuery:   cfg.Postgres.PollUpdates.Query,
 			CacheUpdateTimeout: time.Duration(cfg.Postgres.PollUpdates.Timeout) * time.Millisecond,
+			MetricsEngine:      metricsEngine,
 		}
 		pgEventProducer := postgresEvents.NewPostgresEventProducer(pgEventCfg)
 		fetchInterval := time.Duration(cfg.Postgres.PollUpdates.RefreshRate) * time.Second
diff --git a/stored_requests/events/postgres/database.go b/stored_requests/events/postgres/database.go
index cec9b504..8e1269c6 100644
--- a/stored_requests/events/postgres/database.go
+++ b/stored_requests/events/postgres/database.go
@@ -5,10 +5,12 @@ import (
 	"context"
 	"database/sql"
 	"encoding/json"
+	"net"
 	"time"
 
 	"github.com/golang/glog"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/pbsmetrics"
 	"github.com/prebid/prebid-server/stored_requests/events"
 	"github.com/prebid/prebid-server/util/timeutil"
 )
@@ -17,6 +19,14 @@ func bytesNull() []byte {
 	return []byte{'n', 'u', 'l', 'l'}
 }
 
+var storedDataTypeMetricMap = map[config.DataType]pbsmetrics.StoredDataType{
+	config.RequestDataType:    pbsmetrics.RequestDataType,
+	config.CategoryDataType:   pbsmetrics.CategoryDataType,
+	config.VideoDataType:      pbsmetrics.VideoDataType,
+	config.AMPRequestDataType: pbsmetrics.AMPDataType,
+	config.AccountDataType:    pbsmetrics.AccountDataType,
+}
+
 type PostgresEventProducerConfig struct {
 	DB                 *sql.DB
 	RequestType        config.DataType
@@ -24,6 +34,7 @@ type PostgresEventProducerConfig struct {
 	CacheInitTimeout   time.Duration
 	CacheUpdateQuery   string
 	CacheUpdateTimeout time.Duration
+	MetricsEngine      pbsmetrics.MetricsEngine
 }
 
 type PostgresEventProducer struct {
@@ -51,9 +62,9 @@ func NewPostgresEventProducer(cfg PostgresEventProducerConfig) (eventProducer *P
 func (e *PostgresEventProducer) Run() error {
 	if e.lastUpdate.IsZero() {
 		return e.fetchAll()
-	} else {
-		return e.fetchDelta()
 	}
+
+	return e.fetchDelta()
 }
 
 func (e *PostgresEventProducer) Saves() <-chan events.Save {
@@ -64,60 +75,96 @@ func (e *PostgresEventProducer) Invalidations() <-chan events.Invalidation {
 	return e.invalidations
 }
 
-func (e *PostgresEventProducer) fetchAll() error {
-	thisTimeInUTC := e.time.Now().UTC()
-
+func (e *PostgresEventProducer) fetchAll() (fetchErr error) {
 	timeout := e.cfg.CacheInitTimeout * time.Millisecond
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
+	startTime := e.time.Now().UTC()
 	rows, err := e.cfg.DB.QueryContext(ctx, e.cfg.CacheInitQuery)
+	elapsedTime := time.Since(startTime)
+	e.recordFetchTime(elapsedTime, pbsmetrics.FetchAll)
 
 	if err != nil {
 		glog.Warningf("Failed to fetch all Stored %s data from the DB: %v", e.cfg.RequestType, err)
+		if _, ok := err.(net.Error); ok {
+			e.recordError(pbsmetrics.StoredDataErrorNetwork)
+		} else {
+			e.recordError(pbsmetrics.StoredDataErrorUndefined)
+		}
 		return err
 	}
+
 	defer func() {
 		if err := rows.Close(); err != nil {
 			glog.Warningf("Failed to close the Stored %s DB connection: %v", e.cfg.RequestType, err)
+			e.recordError(pbsmetrics.StoredDataErrorUndefined)
+			fetchErr = err
 		}
 	}()
 	if err := e.sendEvents(rows); err != nil {
 		glog.Warningf("Failed to load all Stored %s data from the DB: %v", e.cfg.RequestType, err)
+		e.recordError(pbsmetrics.StoredDataErrorUndefined)
 		return err
-	} else {
-		e.lastUpdate = thisTimeInUTC
 	}
+
+	e.lastUpdate = startTime
 	return nil
 }
 
-func (e *PostgresEventProducer) fetchDelta() error {
-	thisTimeInUTC := e.time.Now().UTC()
-
+func (e *PostgresEventProducer) fetchDelta() (fetchErr error) {
 	timeout := e.cfg.CacheUpdateTimeout * time.Millisecond
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
+	startTime := e.time.Now().UTC()
 	rows, err := e.cfg.DB.QueryContext(ctx, e.cfg.CacheUpdateQuery, e.lastUpdate)
+	elapsedTime := time.Since(startTime)
+	e.recordFetchTime(elapsedTime, pbsmetrics.FetchDelta)
 
 	if err != nil {
 		glog.Warningf("Failed to fetch updated Stored %s data from the DB: %v", e.cfg.RequestType, err)
+		if _, ok := err.(net.Error); ok {
+			e.recordError(pbsmetrics.StoredDataErrorNetwork)
+		} else {
+			e.recordError(pbsmetrics.StoredDataErrorUndefined)
+		}
 		return err
 	}
+
 	defer func() {
 		if err := rows.Close(); err != nil {
 			glog.Warningf("Failed to close the Stored %s DB connection: %v", e.cfg.RequestType, err)
+			e.recordError(pbsmetrics.StoredDataErrorUndefined)
+			fetchErr = err
 		}
 	}()
 	if err := e.sendEvents(rows); err != nil {
 		glog.Warningf("Failed to load updated Stored %s data from the DB: %v", e.cfg.RequestType, err)
+		e.recordError(pbsmetrics.StoredDataErrorUndefined)
 		return err
-	} else {
-		e.lastUpdate = thisTimeInUTC
 	}
+
+	e.lastUpdate = startTime
 	return nil
 }
 
+func (e *PostgresEventProducer) recordFetchTime(elapsedTime time.Duration, fetchType pbsmetrics.StoredDataFetchType) {
+	e.cfg.MetricsEngine.RecordStoredDataFetchTime(
+		pbsmetrics.StoredDataLabels{
+			DataType:      storedDataTypeMetricMap[e.cfg.RequestType],
+			DataFetchType: fetchType,
+		}, elapsedTime)
+}
+
+func (e *PostgresEventProducer) recordError(errorType pbsmetrics.StoredDataError) {
+	e.cfg.MetricsEngine.RecordStoredDataError(
+		pbsmetrics.StoredDataLabels{
+			DataType: storedDataTypeMetricMap[e.cfg.RequestType],
+			Error:    errorType,
+		})
+}
+
 // sendEvents reads the rows and sends notifications into the channel for any updates.
 // If it returns an error, then callers can be certain that no events were sent to the channels.
 func (e *PostgresEventProducer) sendEvents(rows *sql.Rows) (err error) {


Test patch:
diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-app.json b/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-app.json
new file mode 100644
index 00000000..4b44afcc
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-app.json
@@ -0,0 +1,198 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [{
+        "id": "some_test_ad_id_1",
+        "banner": {
+          "format": [{
+            "w": 300,
+            "h": 250
+          }],
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "25251"
+          }
+        }
+      },
+      {
+        "id": "some_test_ad_id_2",
+        "video":{
+            "mimes": [
+              "video/mp4",
+              "application/javascript"
+            ],
+            "protocols":[
+               2,
+               3,
+               5,
+               6
+            ],
+            "w":640,
+            "h":480
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "25251"
+          }
+        }
+      }],
+      "device": {
+        "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+        "ip": "123.123.123.123",
+        "dnt": 1
+      },
+      "app": {
+        "domain": "www.publisher.com",
+        "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+      }
+    },
+  
+    "httpCalls": [{
+      "expectedRequest": {
+        "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Dnt": [
+            "1"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36"
+          ]
+        },
+        "body": {
+          "id": "some_test_auction",
+          "imp": [{
+            "id": "some_test_ad_id_1",
+            "banner": {
+              "format": [{
+                "w": 300,
+                "h": 250
+              }],
+              "w": 300,
+              "h": 250
+            },
+          "ext": {
+            "bidder": {
+              "tagid": "25251"
+            }
+          },
+            "tagid": "25251",
+            "secure": 0
+          },
+          {
+            "id": "some_test_ad_id_2",
+            "video":{
+                "mimes": [
+                  "video/mp4",
+                  "application/javascript"
+                ],
+                "protocols":[
+                   2,
+                   3,
+                   5,
+                   6
+                ],
+                "w":640,
+                "h":480
+            },
+          "ext": {
+            "bidder": {
+              "tagid": "25251"
+            }
+          },
+            "tagid": "25251",
+            "secure": 0
+          }],
+          "app": {
+            "domain": "www.publisher.com",
+            "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+            "ip": "123.123.123.123",
+            "dnt": 1
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some_test_auction",
+          "seatbid": [
+            {
+              "seat": "12356",
+              "bid": [{
+                "adm": "<div id=\"emximp\"><script>var adm='\\<script\\ src\\=\\'https\\:\\/\\/presentation\\-atl1\\.turn\\.com\\/server\\/ads\\.js\\?code\\=180858515\\&aid\\=49508660\\&tuid\\=3697246472959142409\\&duid\\=3592248512493872937\\&dt\\=a\\&uimpd\\=1OB0KmixN8sHuJc8RPFjxbSZrYW2YZcBuHKg\\-pD5EUJnlrGZTGPpxpl3uYcBYQ7GO937FCfn8dB72mF1jiGRToh8X_UEdA5MP\\-U6ORPQsQ8hmQV0VpZDR7yCaVRZu\\-I8GC4aswLJKm1ehJk2JLqoR8fNGOTt5N02R4_bafcFcCDQehYt_oMETehbgQUsGUuJOUrv\\-rRl46jWaNxtTjLFx01U8qDhPdLKvi2iEiNPYp2pLFfly7qg\\-pjrs0yN8zASsoG5g0qWiuyTjJKZ62irD5SLwF4jQYBxaXIFyF\\-pGADUX2GD6JPzOK9mLaWdX\\-N_fB9gUj8TnVS0WHJ5XJWnCMijjO4qguAQo7LiUftTOQZ66OhXUiNcdI55yMwK0uS_fo2FyHMj2JXiOVpZaJHnWE\\-9QOR3F1E4D9qv9EpOMFAwH1kJqoY\\-G3CBJWC_zUjizw2diGrctqk_tJ9b0jujUs_LmoarsIJcKdWBQoF4xfCjfO7DyaM4l0SBbwLA07GYz0tdDtMd7hbstccVk0PPPVNTOv\\-Pan6yUSHX\\-r\\-U7yDLcFLWxwEaapCh_mKwrATp9oJuOXuK0oIioxIIX9n4raGZPHkl27AX1stb2eIpawf5FkEnwY5YrYIiycI38zuqVAE2ZoI\\-Rn0l8Uckhf1zurAgg6_4AjqZp8oi26jsIHbIc5IsI\\-9UoAoJR_nv_gTojvepaSsA97RYysgopxAW_YzeixMr0LJTvo53DFh4WzrcgaMMPXPf79kdetKmcjPBTPC3bhu9BgI4zAdSobLNCH5BYV1aUcv80K3a1WD7cDZtUXckjWnHt\\-JdzFutuwFnNn66YqhPRxbuWKrW\\-grBHm4T2xv0NxEQIEJxnExroXYCEsPmcki2QTLHbo1fSPgqMF5U4fnfq3\\-3MwNKhtpT4\\-iZN1mzvqeTZPD7TdBWee7k1JXOO_PRyRtwQXaDPQy3HPPvGlRnin_bp26aHCP13HKhn63gh4unBlH7R1xgw0KeaYeBt6C_J9EnIgJQrbpHpwZK0LpM1GSqZ20CbfV891w\\&acp\\=\\$\\{EMX_MACRO\\}\\'\\>\\<\\/script\\><script src=\"https://imp.emxdgt.com/imp/?cp=0.36666&ts=1603222211&adom=www.avis.com&seat=1085&w=320&h=50&pb=0.293328&sid=13835&tid=114507&pid=1764&uid=91441603222210784060e1&wid=15&dom=358899126&tp=${EMX_MACRO}&mt=1&dt=1&st=3&os=&ip=108.17.145.117&sz=&country=US&region=VA&city=Chesapeake&zip=&dma=&agency_id=0&cluster=staging&rf=${RF_MACRO}&cat=1-4&crid=1607428720\"><\\/script>'; window.vc= 'https://imp.emxdgt.com/view/?cp=0.36666&ts=1603222211&adom=www.avis.com&seat=1085&w=320&h=50&pb=0.293328&sid=13835&tid=114507&pid=1764&uid=91441603222210784060e1&wid=15&dom=358899126&tp=${EMX_MACRO}&mt=1&dt=1&st=3&os=&ip=108.17.145.117&sz=&country=US&region=VA&city=Chesapeake&zip=&dma=&agency_id=0&cluster=staging&rf=${RF_MACRO}&cat=1-4&crid=1607428720';  var ap=\"${AUCTION_PRICE}\";var rf=document.domain; window.cp=\"0.36666\";  if(ap == \"test=1\" || ap == \"AUDIT\" || rf == \"s3.amazonaws.com\"){var  finaladm=adm.replace(/\\${EMX_MACRO}/g,\"AUDIT\"); window.audit =  true;}else{var  finaladm=finaladm=adm.replace(/\\${EMX_MACRO}/g,window.cp);}finaladm=finaladm.replace(/\\${RF_MACRO}/g,rf);try{document.write(decodeURI(finaladm));}catch(e){document.write(unescape(finaladm));};</script><script   src=\"https://js.brealtime.com/openvv.js\"></script></div>",
+                "id": "some_test_ad_id_1",
+                "impid": "some_test_ad_id_1",
+                "ttl": 300,
+                "crid": "94395500",
+                "w": 300,
+                "price": 2.942808,
+                "adid": "94395500",
+                "h": 250
+              }]
+          },
+          {
+            "seat": "45678",
+            "bid": [{
+              "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+              "id": "some_test_ad_id_2",
+              "impid": "some_test_ad_id_2",
+              "ttl": 300,
+              "crid": "9999999",
+              "w": 1020,
+              "price": 1,
+              "adid": "9999999",
+              "h": 1000
+            }
+          ]
+        }],
+          "cur": "USD"
+      }
+      }
+    }],
+  
+    "expectedBids": [
+      {
+      "bid": {
+        "adm": "<div id=\"emximp\"><script>var adm='\\<script\\ src\\=\\'https\\:\\/\\/presentation\\-atl1\\.turn\\.com\\/server\\/ads\\.js\\?code\\=180858515\\&aid\\=49508660\\&tuid\\=3697246472959142409\\&duid\\=3592248512493872937\\&dt\\=a\\&uimpd\\=1OB0KmixN8sHuJc8RPFjxbSZrYW2YZcBuHKg\\-pD5EUJnlrGZTGPpxpl3uYcBYQ7GO937FCfn8dB72mF1jiGRToh8X_UEdA5MP\\-U6ORPQsQ8hmQV0VpZDR7yCaVRZu\\-I8GC4aswLJKm1ehJk2JLqoR8fNGOTt5N02R4_bafcFcCDQehYt_oMETehbgQUsGUuJOUrv\\-rRl46jWaNxtTjLFx01U8qDhPdLKvi2iEiNPYp2pLFfly7qg\\-pjrs0yN8zASsoG5g0qWiuyTjJKZ62irD5SLwF4jQYBxaXIFyF\\-pGADUX2GD6JPzOK9mLaWdX\\-N_fB9gUj8TnVS0WHJ5XJWnCMijjO4qguAQo7LiUftTOQZ66OhXUiNcdI55yMwK0uS_fo2FyHMj2JXiOVpZaJHnWE\\-9QOR3F1E4D9qv9EpOMFAwH1kJqoY\\-G3CBJWC_zUjizw2diGrctqk_tJ9b0jujUs_LmoarsIJcKdWBQoF4xfCjfO7DyaM4l0SBbwLA07GYz0tdDtMd7hbstccVk0PPPVNTOv\\-Pan6yUSHX\\-r\\-U7yDLcFLWxwEaapCh_mKwrATp9oJuOXuK0oIioxIIX9n4raGZPHkl27AX1stb2eIpawf5FkEnwY5YrYIiycI38zuqVAE2ZoI\\-Rn0l8Uckhf1zurAgg6_4AjqZp8oi26jsIHbIc5IsI\\-9UoAoJR_nv_gTojvepaSsA97RYysgopxAW_YzeixMr0LJTvo53DFh4WzrcgaMMPXPf79kdetKmcjPBTPC3bhu9BgI4zAdSobLNCH5BYV1aUcv80K3a1WD7cDZtUXckjWnHt\\-JdzFutuwFnNn66YqhPRxbuWKrW\\-grBHm4T2xv0NxEQIEJxnExroXYCEsPmcki2QTLHbo1fSPgqMF5U4fnfq3\\-3MwNKhtpT4\\-iZN1mzvqeTZPD7TdBWee7k1JXOO_PRyRtwQXaDPQy3HPPvGlRnin_bp26aHCP13HKhn63gh4unBlH7R1xgw0KeaYeBt6C_J9EnIgJQrbpHpwZK0LpM1GSqZ20CbfV891w\\&acp\\=\\$\\{EMX_MACRO\\}\\'\\>\\<\\/script\\><script src=\"https://imp.emxdgt.com/imp/?cp=0.36666&ts=1603222211&adom=www.avis.com&seat=1085&w=320&h=50&pb=0.293328&sid=13835&tid=114507&pid=1764&uid=91441603222210784060e1&wid=15&dom=358899126&tp=${EMX_MACRO}&mt=1&dt=1&st=3&os=&ip=108.17.145.117&sz=&country=US&region=VA&city=Chesapeake&zip=&dma=&agency_id=0&cluster=staging&rf=${RF_MACRO}&cat=1-4&crid=1607428720\"><\\/script>'; window.vc= 'https://imp.emxdgt.com/view/?cp=0.36666&ts=1603222211&adom=www.avis.com&seat=1085&w=320&h=50&pb=0.293328&sid=13835&tid=114507&pid=1764&uid=91441603222210784060e1&wid=15&dom=358899126&tp=${EMX_MACRO}&mt=1&dt=1&st=3&os=&ip=108.17.145.117&sz=&country=US&region=VA&city=Chesapeake&zip=&dma=&agency_id=0&cluster=staging&rf=${RF_MACRO}&cat=1-4&crid=1607428720';  var ap=\"${AUCTION_PRICE}\";var rf=document.domain; window.cp=\"0.36666\";  if(ap == \"test=1\" || ap == \"AUDIT\" || rf == \"s3.amazonaws.com\"){var  finaladm=adm.replace(/\\${EMX_MACRO}/g,\"AUDIT\"); window.audit =  true;}else{var  finaladm=finaladm=adm.replace(/\\${EMX_MACRO}/g,window.cp);}finaladm=finaladm.replace(/\\${RF_MACRO}/g,rf);try{document.write(decodeURI(finaladm));}catch(e){document.write(unescape(finaladm));};</script><script   src=\"https://js.brealtime.com/openvv.js\"></script></div>",
+        "id": "some_test_ad_id_1",
+        "impid": "some_test_ad_id_1",
+        "ttl": 300,
+        "crid": "94395500",
+        "w": 300,
+        "price": 2.942808,
+        "adid": "94395500",
+        "h": 250
+      },
+      "type": "banner"
+    },
+    {
+      "bid": {
+        "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+        "id": "some_test_ad_id_2",
+        "impid": "some_test_ad_id_2",
+        "ttl": 300,
+        "crid": "9999999",
+        "w": 1020,
+        "price": 1,
+        "adid": "9999999",
+        "h": 1000
+      },
+      "type": "video"
+    }
+  ]
+}
+  
\ No newline at end of file
diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json b/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json
new file mode 100644
index 00000000..c2b20cf1
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json
@@ -0,0 +1,200 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [{
+        "id": "some_test_ad_id_1",
+        "banner": {
+          "format": [{
+            "w": 300,
+            "h": 250
+          }],
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "25251"
+          }
+        }
+      },
+      {
+        "id": "some_test_ad_id_2",
+        "video":{
+            "mimes": [
+              "video/mp4",
+              "application/javascript"
+            ],
+            "protocols":[
+               2,
+               3,
+               5,
+               6
+            ],
+            "w":640,
+            "h":480
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "25251"
+          }
+        }
+      }
+    ],
+      "device": {
+        "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+        "ip": "123.123.123.123",
+        "dnt": 1
+      },
+      "site": {
+        "domain": "www.publisher.com",
+        "page": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+      }
+    },
+  
+    "httpCalls": [{
+      "expectedRequest": {
+        "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Referer": [
+            "http://www.publisher.com/awesome/site?with=some&parameters=here"
+          ],
+          "Dnt": [
+            "1"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36"
+          ]
+        },
+        "body": {
+          "id": "some_test_auction",
+          "imp": [{
+            "id": "some_test_ad_id_1",
+            "banner": {
+              "format": [{
+                "w": 300,
+                "h": 250
+              }],
+              "w": 300,
+              "h": 250
+            },
+            "ext": {
+              "bidder": {
+                "tagid": "25251"
+              }
+            },
+            "tagid": "25251",
+            "secure": 0
+          },
+          {
+            "id": "some_test_ad_id_2",
+            "video":{
+                "mimes": [
+                  "video/mp4",
+                  "application/javascript"
+                ],
+                "protocols":[
+                   2,
+                   3,
+                   5,
+                   6
+                ],
+                "w":640,
+                "h":480
+            },
+            "ext": {
+              "bidder": {
+                "tagid": "25251"
+              }
+            },
+            "tagid": "25251",
+            "secure": 0
+          }],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+            "ip": "123.123.123.123",
+            "dnt": 1
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some_test_auction",
+          "seatbid": [{
+              "seat": "12356",
+              "bid": [{
+                "adm": "<div id=\"emximp\"><script>var adm='\\<script\\ src\\=\\'https\\:\\/\\/presentation\\-atl1\\.turn\\.com\\/server\\/ads\\.js\\?code\\=180858515\\&aid\\=49508660\\&tuid\\=3697246472959142409\\&duid\\=3592248512493872937\\&dt\\=a\\&uimpd\\=1OB0KmixN8sHuJc8RPFjxbSZrYW2YZcBuHKg\\-pD5EUJnlrGZTGPpxpl3uYcBYQ7GO937FCfn8dB72mF1jiGRToh8X_UEdA5MP\\-U6ORPQsQ8hmQV0VpZDR7yCaVRZu\\-I8GC4aswLJKm1ehJk2JLqoR8fNGOTt5N02R4_bafcFcCDQehYt_oMETehbgQUsGUuJOUrv\\-rRl46jWaNxtTjLFx01U8qDhPdLKvi2iEiNPYp2pLFfly7qg\\-pjrs0yN8zASsoG5g0qWiuyTjJKZ62irD5SLwF4jQYBxaXIFyF\\-pGADUX2GD6JPzOK9mLaWdX\\-N_fB9gUj8TnVS0WHJ5XJWnCMijjO4qguAQo7LiUftTOQZ66OhXUiNcdI55yMwK0uS_fo2FyHMj2JXiOVpZaJHnWE\\-9QOR3F1E4D9qv9EpOMFAwH1kJqoY\\-G3CBJWC_zUjizw2diGrctqk_tJ9b0jujUs_LmoarsIJcKdWBQoF4xfCjfO7DyaM4l0SBbwLA07GYz0tdDtMd7hbstccVk0PPPVNTOv\\-Pan6yUSHX\\-r\\-U7yDLcFLWxwEaapCh_mKwrATp9oJuOXuK0oIioxIIX9n4raGZPHkl27AX1stb2eIpawf5FkEnwY5YrYIiycI38zuqVAE2ZoI\\-Rn0l8Uckhf1zurAgg6_4AjqZp8oi26jsIHbIc5IsI\\-9UoAoJR_nv_gTojvepaSsA97RYysgopxAW_YzeixMr0LJTvo53DFh4WzrcgaMMPXPf79kdetKmcjPBTPC3bhu9BgI4zAdSobLNCH5BYV1aUcv80K3a1WD7cDZtUXckjWnHt\\-JdzFutuwFnNn66YqhPRxbuWKrW\\-grBHm4T2xv0NxEQIEJxnExroXYCEsPmcki2QTLHbo1fSPgqMF5U4fnfq3\\-3MwNKhtpT4\\-iZN1mzvqeTZPD7TdBWee7k1JXOO_PRyRtwQXaDPQy3HPPvGlRnin_bp26aHCP13HKhn63gh4unBlH7R1xgw0KeaYeBt6C_J9EnIgJQrbpHpwZK0LpM1GSqZ20CbfV891w\\&acp\\=\\$\\{EMX_MACRO\\}\\'\\>\\<\\/script\\><script src=\"https://imp.emxdgt.com/imp/?cp=0.36666&ts=1603222211&adom=www.avis.com&seat=1085&w=320&h=50&pb=0.293328&sid=13835&tid=114507&pid=1764&uid=91441603222210784060e1&wid=15&dom=358899126&tp=${EMX_MACRO}&mt=1&dt=1&st=3&os=&ip=108.17.145.117&sz=&country=US&region=VA&city=Chesapeake&zip=&dma=&agency_id=0&cluster=staging&rf=${RF_MACRO}&cat=1-4&crid=1607428720\"><\\/script>'; window.vc= 'https://imp.emxdgt.com/view/?cp=0.36666&ts=1603222211&adom=www.avis.com&seat=1085&w=320&h=50&pb=0.293328&sid=13835&tid=114507&pid=1764&uid=91441603222210784060e1&wid=15&dom=358899126&tp=${EMX_MACRO}&mt=1&dt=1&st=3&os=&ip=108.17.145.117&sz=&country=US&region=VA&city=Chesapeake&zip=&dma=&agency_id=0&cluster=staging&rf=${RF_MACRO}&cat=1-4&crid=1607428720';  var ap=\"${AUCTION_PRICE}\";var rf=document.domain; window.cp=\"0.36666\";  if(ap == \"test=1\" || ap == \"AUDIT\" || rf == \"s3.amazonaws.com\"){var  finaladm=adm.replace(/\\${EMX_MACRO}/g,\"AUDIT\"); window.audit =  true;}else{var  finaladm=finaladm=adm.replace(/\\${EMX_MACRO}/g,window.cp);}finaladm=finaladm.replace(/\\${RF_MACRO}/g,rf);try{document.write(decodeURI(finaladm));}catch(e){document.write(unescape(finaladm));};</script><script   src=\"https://js.brealtime.com/openvv.js\"></script></div>",
+                "id": "some_test_ad_id_1",
+                "impid": "some_test_ad_id_1",
+                "ttl": 300,
+                "crid": "94395500",
+                "w": 300,
+                "price": 2.942808,
+                "adid": "94395500",
+                "h": 250
+              }]
+          },
+          {
+            "seat": "45678",
+            "bid": [{
+              "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+              "id": "some_test_ad_id_2",
+              "impid": "some_test_ad_id_2",
+              "ttl": 300,
+              "crid": "9999999",
+              "w": 1020,
+              "price": 1,
+              "adid": "9999999",
+              "h": 1000
+            }
+          ]
+        }],
+          "cur": "USD"
+      }
+      }
+    }],
+  
+    "expectedBids": [{
+      "bid": {
+        "adm": "<div id=\"emximp\"><script>var adm='\\<script\\ src\\=\\'https\\:\\/\\/presentation\\-atl1\\.turn\\.com\\/server\\/ads\\.js\\?code\\=180858515\\&aid\\=49508660\\&tuid\\=3697246472959142409\\&duid\\=3592248512493872937\\&dt\\=a\\&uimpd\\=1OB0KmixN8sHuJc8RPFjxbSZrYW2YZcBuHKg\\-pD5EUJnlrGZTGPpxpl3uYcBYQ7GO937FCfn8dB72mF1jiGRToh8X_UEdA5MP\\-U6ORPQsQ8hmQV0VpZDR7yCaVRZu\\-I8GC4aswLJKm1ehJk2JLqoR8fNGOTt5N02R4_bafcFcCDQehYt_oMETehbgQUsGUuJOUrv\\-rRl46jWaNxtTjLFx01U8qDhPdLKvi2iEiNPYp2pLFfly7qg\\-pjrs0yN8zASsoG5g0qWiuyTjJKZ62irD5SLwF4jQYBxaXIFyF\\-pGADUX2GD6JPzOK9mLaWdX\\-N_fB9gUj8TnVS0WHJ5XJWnCMijjO4qguAQo7LiUftTOQZ66OhXUiNcdI55yMwK0uS_fo2FyHMj2JXiOVpZaJHnWE\\-9QOR3F1E4D9qv9EpOMFAwH1kJqoY\\-G3CBJWC_zUjizw2diGrctqk_tJ9b0jujUs_LmoarsIJcKdWBQoF4xfCjfO7DyaM4l0SBbwLA07GYz0tdDtMd7hbstccVk0PPPVNTOv\\-Pan6yUSHX\\-r\\-U7yDLcFLWxwEaapCh_mKwrATp9oJuOXuK0oIioxIIX9n4raGZPHkl27AX1stb2eIpawf5FkEnwY5YrYIiycI38zuqVAE2ZoI\\-Rn0l8Uckhf1zurAgg6_4AjqZp8oi26jsIHbIc5IsI\\-9UoAoJR_nv_gTojvepaSsA97RYysgopxAW_YzeixMr0LJTvo53DFh4WzrcgaMMPXPf79kdetKmcjPBTPC3bhu9BgI4zAdSobLNCH5BYV1aUcv80K3a1WD7cDZtUXckjWnHt\\-JdzFutuwFnNn66YqhPRxbuWKrW\\-grBHm4T2xv0NxEQIEJxnExroXYCEsPmcki2QTLHbo1fSPgqMF5U4fnfq3\\-3MwNKhtpT4\\-iZN1mzvqeTZPD7TdBWee7k1JXOO_PRyRtwQXaDPQy3HPPvGlRnin_bp26aHCP13HKhn63gh4unBlH7R1xgw0KeaYeBt6C_J9EnIgJQrbpHpwZK0LpM1GSqZ20CbfV891w\\&acp\\=\\$\\{EMX_MACRO\\}\\'\\>\\<\\/script\\><script src=\"https://imp.emxdgt.com/imp/?cp=0.36666&ts=1603222211&adom=www.avis.com&seat=1085&w=320&h=50&pb=0.293328&sid=13835&tid=114507&pid=1764&uid=91441603222210784060e1&wid=15&dom=358899126&tp=${EMX_MACRO}&mt=1&dt=1&st=3&os=&ip=108.17.145.117&sz=&country=US&region=VA&city=Chesapeake&zip=&dma=&agency_id=0&cluster=staging&rf=${RF_MACRO}&cat=1-4&crid=1607428720\"><\\/script>'; window.vc= 'https://imp.emxdgt.com/view/?cp=0.36666&ts=1603222211&adom=www.avis.com&seat=1085&w=320&h=50&pb=0.293328&sid=13835&tid=114507&pid=1764&uid=91441603222210784060e1&wid=15&dom=358899126&tp=${EMX_MACRO}&mt=1&dt=1&st=3&os=&ip=108.17.145.117&sz=&country=US&region=VA&city=Chesapeake&zip=&dma=&agency_id=0&cluster=staging&rf=${RF_MACRO}&cat=1-4&crid=1607428720';  var ap=\"${AUCTION_PRICE}\";var rf=document.domain; window.cp=\"0.36666\";  if(ap == \"test=1\" || ap == \"AUDIT\" || rf == \"s3.amazonaws.com\"){var  finaladm=adm.replace(/\\${EMX_MACRO}/g,\"AUDIT\"); window.audit =  true;}else{var  finaladm=finaladm=adm.replace(/\\${EMX_MACRO}/g,window.cp);}finaladm=finaladm.replace(/\\${RF_MACRO}/g,rf);try{document.write(decodeURI(finaladm));}catch(e){document.write(unescape(finaladm));};</script><script   src=\"https://js.brealtime.com/openvv.js\"></script></div>",
+        "id": "some_test_ad_id_1",
+        "impid": "some_test_ad_id_1",
+        "ttl": 300,
+        "crid": "94395500",
+        "w": 300,
+        "price": 2.942808,
+        "adid": "94395500",
+        "h": 250
+      },
+      "type": "banner"
+    },
+    {
+      "bid": {
+        "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+        "id": "some_test_ad_id_2",
+        "impid": "some_test_ad_id_2",
+        "ttl": 300,
+        "crid": "9999999",
+        "w": 1020,
+        "price": 1,
+        "adid": "9999999",
+        "h": 1000
+      },
+      "type": "video"
+    }
+  ]
+}
+  
\ No newline at end of file
diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json b/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json
new file mode 100644
index 00000000..8de90f52
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json
@@ -0,0 +1,119 @@
+{
+  "mockBidRequest": {
+    "id": "some_test_auction",
+    "imp": [{
+      "id": "some_test_ad_id",
+      "banner": {
+        "format": [{
+          "w": 300,
+          "h": 250
+        }],
+        "w": 300,
+        "h": 250
+      },
+      "ext": {
+        "bidder": {
+          "tagid": "25251"
+        }
+      }
+    }],
+    "device": {
+      "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+      "ip": "123.123.123.123",
+      "dnt": 1
+    },
+    "app": {
+      "domain": "www.publisher.com",
+      "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+    }
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+      "headers": {
+        "Accept": [
+          "application/json"
+        ],
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "X-Forwarded-For": [
+          "123.123.123.123"
+        ],
+        "Dnt": [
+          "1"
+        ],
+        "User-Agent": [
+          "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36"
+        ]
+      },
+      "body": {
+        "id": "some_test_auction",
+        "imp": [{
+          "id": "some_test_ad_id",
+          "banner": {
+            "format": [{
+              "w": 300,
+              "h": 250
+            }],
+            "w": 300,
+            "h": 250
+          },
+          "ext": {
+            "bidder": {
+              "tagid": "25251"
+            }
+          },
+          "tagid": "25251",
+          "secure": 0
+        }],
+        "app": {
+          "domain": "www.publisher.com",
+          "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+        },
+        "device": {
+          "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+          "ip": "123.123.123.123",
+          "dnt": 1
+        }
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": {
+        "id": "some_test_auction",
+        "seatbid": [{
+            "seat": "12356",
+            "bid": [{
+              "adm": "<div id=\"123456789_ad\"><script>!function(){console.log\"Hello, world.\";}();<\/script><\/div>",
+              "id": "some_test_ad_id",
+              "impid": "some_test_ad_id",
+              "ttl": 300,
+              "crid": "94395500",
+              "w": 300,
+              "price": 2.942808,
+              "adid": "94395500",
+              "h": 250
+            }]
+        }],
+        "cur": "USD"
+    }
+    }
+  }],
+
+  "expectedBids": [{
+    "bid": {
+      "adm": "<div id=\"123456789_ad\"><script>!function(){console.log\"Hello, world.\";}();<\/script><\/div>",
+      "id": "some_test_ad_id",
+      "impid": "some_test_ad_id",
+      "ttl": 300,
+      "crid": "94395500",
+      "w": 300,
+      "price": 2.942808,
+      "adid": "94395500",
+      "h": 250
+    },
+    "type": "banner"
+  }]
+}
diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/minimal-banner.json b/adapters/emx_digital/emx_digitaltest/exemplary/minimal-banner.json
index 33a96cb4..855bdf2e 100644
--- a/adapters/emx_digital/emx_digitaltest/exemplary/minimal-banner.json
+++ b/adapters/emx_digital/emx_digitaltest/exemplary/minimal-banner.json
@@ -63,6 +63,11 @@
             "w": 300,
             "h": 250
           },
+          "ext": {
+            "bidder": {
+              "tagid": "25251"
+            }
+          },
           "tagid": "25251",
           "secure": 0
         }],
@@ -85,7 +90,7 @@
             "seat": "12356",
             "bid": [{
               "adm": "<div id=\"123456789_ad\"><script>!function(){console.log\"Hello, world.\";}();<\/script><\/div>",
-              "id": "uuid",
+              "id": "some_test_ad_id",
               "impid": "some_test_ad_id",
               "ttl": 300,
               "crid": "94395500",
@@ -103,7 +108,7 @@
   "expectedBids": [{
     "bid": {
       "adm": "<div id=\"123456789_ad\"><script>!function(){console.log\"Hello, world.\";}();<\/script><\/div>",
-      "id": "uuid",
+      "id": "some_test_ad_id",
       "impid": "some_test_ad_id",
       "ttl": 300,
       "crid": "94395500",
@@ -114,4 +119,4 @@
     },
     "type": "banner"
   }]
-}
\ No newline at end of file
+}
diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/video-app.json b/adapters/emx_digital/emx_digitaltest/exemplary/video-app.json
new file mode 100644
index 00000000..ebc313a2
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/exemplary/video-app.json
@@ -0,0 +1,132 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [{
+        "id": "some_test_ad_id",
+        "video":{
+            "mimes": [
+              "video/mp4",
+              "application/javascript"
+            ],
+            "protocols":[
+               2,
+               3,
+               5,
+               6
+            ],
+            "w":640,
+            "h":480
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "25251"
+          }
+        }
+      }],
+      "device": {
+        "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+        "ip": "123.123.123.123",
+        "dnt": 1
+      },
+      "app": {
+        "domain": "www.publisher.com",
+        "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+      }
+    },
+  
+    "httpCalls": [{
+      "expectedRequest": {
+        "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Dnt": [
+            "1"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36"
+          ]
+        },
+        "body": {
+          "id": "some_test_auction",
+          "imp": [{
+            "id": "some_test_ad_id",
+            "video":{
+                "mimes": [
+                  "video/mp4",
+                  "application/javascript"
+                ],
+                "protocols":[
+                   2,
+                   3,
+                   5,
+                   6
+                ],
+                "w":640,
+                "h":480
+            },
+            "ext": {
+              "bidder": {
+                "tagid": "25251"
+              }
+            },
+            "tagid": "25251",
+            "secure": 0
+          }],
+          "app": {
+            "domain": "www.publisher.com",
+            "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+            "ip": "123.123.123.123",
+            "dnt": 1
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some_test_auction",
+          "seatbid": [{
+              "seat": "12356",
+              "bid": [{
+                "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                "id": "some_test_ad_id",
+                "impid": "some_test_ad_id",
+                "ttl": 300,
+                "crid": "148479785",
+                "w": 640,
+                "price": 0.05,
+                "adid": "148479785",
+                "h": 480
+              }]
+          }],
+          "cur": "USD"
+      }
+      }
+    }],
+  
+    "expectedBids": [{
+      "bid": {
+        "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+        "id": "some_test_ad_id",
+        "impid": "some_test_ad_id",
+        "ttl": 300,
+        "crid": "148479785",
+        "w": 640,
+        "price": 0.05,
+        "adid": "148479785",
+        "h": 480
+      },
+      "type": "video"
+    }]
+  }
+  
\ No newline at end of file
diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/video-ctv.json b/adapters/emx_digital/emx_digitaltest/exemplary/video-ctv.json
new file mode 100644
index 00000000..66a6413d
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/exemplary/video-ctv.json
@@ -0,0 +1,150 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [{
+        "id": "some_test_ad_id",
+        "video":{
+            "mimes": [
+              "video/mp4",
+              "application/javascript"
+            ],
+            "protocols":[
+               2,
+               3,
+               5,
+               6,
+               7
+            ],
+            "w": 1920,
+            "h": 1080
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "25251"
+          }
+        }
+      }],
+      "device": {
+        "ua": "Roku\/DVP-9.0 (919.00E04142A)",
+        "ip": "123.123.123.123",
+        "dnt": 1,
+        "devicetype": 3,
+        "make": "Roku",
+        "model": "Digital Video player",
+        "language": "en",
+        "ifa": "7a2016d7-17b0-572f-a7a6-13f89a81d905"
+      },
+      "app": {
+        "domain": "www.publisher.com",
+        "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here",
+        "name": "Example TV",
+        "bundle": "com.example.tv"
+      }
+    },
+  
+    "httpCalls": [{
+      "expectedRequest": {
+        "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Accept-Language": [
+            "en"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Dnt": [
+            "1"
+          ],
+          "User-Agent": [
+            "Roku\/DVP-9.0 (919.00E04142A)"
+          ]
+        },
+        "body": {
+          "id": "some_test_auction",
+          "imp": [{
+            "id": "some_test_ad_id",
+            "video":{
+                "mimes": [
+                  "video/mp4",
+                  "application/javascript"
+                ],
+                "protocols":[
+                   2,
+                   3,
+                   5,
+                   6
+                ],
+                "w":1920,
+                "h":1080
+            },
+            "ext": {
+              "bidder": {
+                "tagid": "25251"
+              }
+            },
+            "tagid": "25251",
+            "secure": 0
+          }],
+          "app": {
+            "domain": "www.publisher.com",
+            "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here",
+            "name": "Example TV",
+            "bundle": "com.example.tv"
+          },
+          "device": {
+            "ua": "Roku\/DVP-9.0 (919.00E04142A)",
+            "ip": "123.123.123.123",
+            "dnt": 1,
+            "devicetype": 3,
+            "make": "Roku",
+            "model": "Digital Video player",
+            "language": "en",
+            "ifa": "7a2016d7-17b0-572f-a7a6-13f89a81d905"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some_test_auction",
+          "seatbid": [{
+              "seat": "12356",
+              "bid": [{
+                "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                "id": "some_test_ad_id",
+                "impid": "some_test_ad_id",
+                "ttl": 300,
+                "crid": "148479785",
+                "w": 1920,
+                "price": 1.00,
+                "adid": "148479785",
+                "h": 1080
+              }]
+          }],
+          "cur": "USD"
+      }
+      }
+    }],
+  
+    "expectedBids": [{
+      "bid": {
+        "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+        "id": "some_test_ad_id",
+        "impid": "some_test_ad_id",
+        "ttl": 300,
+        "crid": "148479785",
+        "w": 1920,
+        "price": 1.00,
+        "adid": "148479785",
+        "h": 1080
+      },
+      "type": "video"
+    }]
+  }
+  
\ No newline at end of file
diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/video-site.json b/adapters/emx_digital/emx_digitaltest/exemplary/video-site.json
new file mode 100644
index 00000000..30a1167b
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/exemplary/video-site.json
@@ -0,0 +1,135 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [{
+        "id": "some_test_ad_id",
+        "video":{
+            "mimes": [
+              "video/mp4",
+              "application/javascript"
+            ],
+            "protocols":[
+               2,
+               3,
+               5,
+               6
+            ],
+            "w":640,
+            "h":480
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "25251"
+          }
+        }
+      }],
+      "device": {
+        "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+        "ip": "123.123.123.123",
+        "dnt": 1
+      },
+      "site": {
+        "domain": "www.publisher.com",
+        "page": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+      }
+    },
+  
+    "httpCalls": [{
+      "expectedRequest": {
+        "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Referer": [
+            "http://www.publisher.com/awesome/site?with=some&parameters=here"
+          ],
+          "Dnt": [
+            "1"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36"
+          ]
+        },
+        "body": {
+          "id": "some_test_auction",
+          "imp": [{
+            "id": "some_test_ad_id",
+            "video":{
+                "mimes": [
+                  "video/mp4",
+                  "application/javascript"
+                ],
+                "protocols":[
+                   2,
+                   3,
+                   5,
+                   6
+                ],
+                "w":640,
+                "h":480
+            },
+            "ext": {
+              "bidder": {
+                "tagid": "25251"
+              }
+            },
+            "tagid": "25251",
+            "secure": 0
+          }],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+            "ip": "123.123.123.123",
+            "dnt": 1
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some_test_auction",
+          "seatbid": [{
+              "seat": "12356",
+              "bid": [{
+                "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                "id": "some_test_ad_id",
+                "impid": "some_test_ad_id",
+                "ttl": 300,
+                "crid": "148479785",
+                "w": 640,
+                "price": 0.05,
+                "adid": "148479785",
+                "h": 480
+              }]
+          }],
+          "cur": "USD"
+      }
+      }
+    }],
+  
+    "expectedBids": [{
+      "bid": {
+        "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><AdParameters><![CDATA[{\"adType\":\"VIDEO\",\"ad\":{\"viewTrk\":\"https%3A%2F%2Fimp.emxdgt.com%2Fview%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044\",\"tag\":\"%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22yes%22%3F%3E%3CVAST%20version%3D%223.0%22%3E%3CAd%20id%3D%22209610044%22%3E%3CWrapper%3E%3CAdSystem%20version%3D%223.0%22%3Eadnxs%3C%2FAdSystem%3E%3CVASTAdTagURI%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fnym1-ib.adnxs.com%2Fab%3Fan_audit%3D0%26referrer%3Dhttps%253A%252F%252Fwww.brit.co%252Fthese-actors-cant-escape-their-famous-lines%252F%26e%3DwqT_3QLkCvBDZAUAAAMA1gAFAQir14XzBRCQ2fHY6OTvjT4YhqCpw5iblL0LKjYJR6zFpwAAPkARR6zFpwAAPkAZAAAA4FE4NkAhR6wJGwApESTIMQAAAAAp3CNAMPqI3Qc4zApAzApIAlC8yvljWL_Bc2AAaIrFCni14gSAAQGKAQNVU0SSBQbwPpgBAaABAagBAbABALgBA8ABBMgBAtABCdgBAOABAPABAIoCO3VmKCdhJywgMjg5MTAxLCAxNTgzNDQyODU5KQUcAHIBHCAwOTYxMDA0NCwyHwD0FwGSAt0CIXB6OWpKZ2l0b3NvU0VMekstV01ZQUNDX3dYTXdBRGdBUUFSSXpBcFEtb2pkQjFnQVlMNENhQUJ3QUhnQWdBRUFpQUVBa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUh0QktEMkFBQS1RTUVCN1FTZzlnQUFQa0RKQVZ3MUt3WjloQUJBMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcmFMS0Vyb0RDVTVaVFRJNk5ERXlNLUFEZ1J5SUJBQ1FCQUNZQkFHeUJBb0kwN2JfQ1JDNTRjb013UVFBQUFBQUFBQUFBTWtFQUFBBWcQQUFEUkINCiB3Q0ZBMkFRQTguLAAgSWdGbXlDcEJRDSUUUEFfc1FVDQ0BASwuLpoCiQEhYkJBdko6YQEkdjhGeklBUW9BRBH9XEEtUURvSlRsbE5Nam8wTVRJelFJRWNTUQFJBEFBAV0AVREMDEFBQVcdDABZHQwAYR0MAGMNDAG98LBlQUEu2AKsA-ACiZ1S6gJAaHR0cHM6Ly93d3cuYnJpdC5jby90aGVzZS1hY3RvcnMtY2FudC1lc2NhcGUtdGhlaXItZmFtb3VzLWxpbmVzL_ICEAoGQURWX0lEEgYyODkxMDHyAhIKBkNQR19JRBIIMTExMDg3NznyAhEKBUNQX0lEEggzODk2NTU0OfICDQoIQURWX0ZSRVESATDyAg0KCFJFTV9VU0VSEgEw8gIMCggBIDRDT0RFEgDyAgwKCENQRxUPEAsKB0NQFQ4cCQoFSU9fSUQBKRALCgdJTxUaOBMKD0NVU1RPTV9NT0RFTA0kCBoKFjIWABxMRUFGX05BTQVqCB4KGjYdAAhBU1QBPgxJRklFBWIcDQoIU1BMSVQBTfCLATCAAwCIAwGQAwCYAxSgAwGqAwDAA-CoAcgDANgD3q434AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQOMTA0LjE2Mi41MC4yNDWoBAGyBBIIARAQGNwGIOQDKAEoAjAAOAO4BADABADIBIGhggHSBA4xMzU2I05ZTTI6NDEyM9oEAggB4AQA8ASBSgz6BBIJgWgQiFxEQBGBjDDpdVLAiAUBmAUAoAX_EQEgAaoFFjI5NDUxkSc0NzI3NDQ2MmUxwAUAyQUBQRgAAPA_0gUJBU0BAXDYBQHgBQHwBYCvM_oFBAgAEACQBgGYBgC4BgDBBgEhATEg0AbCBNoGFgoQCREZAVAQABgA4AYE8gYCCACABwGIBwCgB0A.%26s%3Dc26c363d17931ce09fc5383e6f8aa1ff7e9324ca%26pp%3D%24%7BEMX_MACRO%7D%5D%5D%3E%3C%2FVASTAdTagURI%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fib.adnxs.com%2Fnop%5D%5D%3E%3C%2FImpression%3E%3CCreatives%3E%3CCreative%20adID%3D%22209610044%22%3E%3CLinear%3E%3C%2FLinear%3E%3C%2FCreative%3E%3C%2FCreatives%3E%3CImpression%3E%3C%21%5BCDATA%5Bhttps%3A%2F%2Fimp.emxdgt.com%2Fimp%2F%3Fcp%3D30.0000%26ts%3D1583442857%26adom%3Dhttps%3A%2F%2Femxdigital.com%26seat%3D1356%26w%3D5%26h%3D5%26pb%3D24%26sid%3D7732%26tid%3D71423%26pid%3D1158%26uid%3D29451583442857274462e1%26wid%3D1%26dom%3Dwww.brit.co%26tp%3D%24%7BEMX_MACRO%7D%26rf%3D%24%7BRF_MACRO%7D%26cat%3D3-1%26crid%3D209610044%5D%5D%3E%3C%2FImpression%3E%3C%2FWrapper%3E%3C%2FAd%3E%3C%2FVAST%3E\",\"avg\": \"30.0000\", \"auction\" : \"${AUCTION_PRICE}\"}}]]></AdParameters><MediaFiles><MediaFile delivery=\"progressive\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[https://js.brealtime.com/ovvbundle-vcr_1.1.js]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
+        "id": "some_test_ad_id",
+        "impid": "some_test_ad_id",
+        "ttl": 300,
+        "crid": "148479785",
+        "w": 640,
+        "price": 0.05,
+        "adid": "148479785",
+        "h": 480
+      },
+      "type": "video"
+    }]
+  }
+  
\ No newline at end of file
diff --git a/adapters/emx_digital/emx_digitaltest/params/race/video.json b/adapters/emx_digital/emx_digitaltest/params/race/video.json
new file mode 100644
index 00000000..d3e484af
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/params/race/video.json
@@ -0,0 +1,4 @@
+{
+    "tagid": "25251",
+    "bidfloor": "0.01"
+}
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/add-bidfloor.json b/adapters/emx_digital/emx_digitaltest/supplemental/add-bidfloor.json
index 98e04887..a4041a1d 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/add-bidfloor.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/add-bidfloor.json
@@ -25,6 +25,12 @@
                         "h": 728,
                         "w": 90
                     },
+                    "ext": {
+                        "bidder": {
+                            "tagid": "1234568",
+                            "bidfloor": "1.25"
+                        }
+                    },
                     "id": "some_test_ad",
                     "secure": 0,
                     "tagid": "1234568",
@@ -40,4 +46,4 @@
           }
         }
     }]
-}
\ No newline at end of file
+}
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/app-domain-and-url-correctly-parsed.json b/adapters/emx_digital/emx_digitaltest/supplemental/app-domain-and-url-correctly-parsed.json
new file mode 100644
index 00000000..5fd563dd
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/app-domain-and-url-correctly-parsed.json
@@ -0,0 +1,66 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [{
+              "w": 300,
+              "h": 600
+          }],
+            "w": 300,
+            "h": 600
+          },
+          "ext": {
+            "bidder": {
+              "tagid": "123456"
+            }
+          }
+        }
+      ],
+      "app": {
+        "domain": "www.publisher.com",
+        "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+      }
+    },
+
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+          "body": {
+            "id": "some_test_auction",
+            "imp": [{
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [{
+                        "w": 300,
+                        "h": 600
+                    }],
+                    "w": 300,
+                    "h": 600
+                },
+                "ext": {
+                  "bidder": {
+                    "tagid": "123456"
+                  }
+                },
+                "tagid": "123456",
+                "secure": 0
+            }],
+            "app": {
+                "domain": "www.publisher.com",
+                "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+            }
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+
+          }
+        }
+      }
+    ]
+  }
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/app-storeUrl-correctly-parsed.json b/adapters/emx_digital/emx_digitaltest/supplemental/app-storeUrl-correctly-parsed.json
new file mode 100644
index 00000000..8c8fe35b
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/app-storeUrl-correctly-parsed.json
@@ -0,0 +1,64 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [{
+              "w": 300,
+              "h": 600
+          }],
+            "w": 300,
+            "h": 600
+          },
+          "ext": {
+            "bidder": {
+              "tagid": "123456"
+            }
+          }
+        }
+      ],
+      "app": {
+        "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+      }
+    },
+
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+          "body": {
+            "id": "some_test_auction",
+            "imp": [{
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [{
+                        "w": 300,
+                        "h": 600
+                    }],
+                    "w": 300,
+                    "h": 600
+                },
+                "ext": {
+                  "bidder": {
+                    "tagid": "123456"
+                  }
+                },
+                "tagid": "123456",
+                "secure": 0
+            }],
+            "app": {
+                "storeurl": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+            }
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+
+          }
+        }
+      }
+    ]
+  }
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-banner-missing-sizes.json b/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-banner-missing-sizes.json
index 975d5335..3265abd1 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-banner-missing-sizes.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-banner-missing-sizes.json
@@ -28,4 +28,4 @@
       "comparison": "literal"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-ext-tagid-value.json b/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-ext-tagid-value.json
index 4116c09d..279b007e 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-ext-tagid-value.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-ext-tagid-value.json
@@ -30,4 +30,4 @@
       "comparison": "literal"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-mimes.json b/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-mimes.json
new file mode 100644
index 00000000..cf027cbf
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-mimes.json
@@ -0,0 +1,34 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "video": {
+              "w":350,
+              "h":250
+          },
+          "ext": {
+            "bidder": {
+              "tagid": "12356"
+            }
+          }
+        }
+      ],
+      "site": {
+        "domain": "www.publisher.com",
+        "page": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+      }
+    },
+    "expectedMakeRequestsErrors": [
+      {
+        "value": "Video: missing required field mimes",
+        "comparison": "literal"
+      },
+      {
+        "value": "Error in preprocess of Imp, err: [Video: missing required field mimes]",
+        "comparison": "literal"
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-sizes.json b/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-sizes.json
new file mode 100644
index 00000000..3316a553
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-sizes.json
@@ -0,0 +1,36 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "video": {
+            "mimes": [
+              "video/mp4",
+              "application/javascript"
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "tagid": "12356"
+            }
+          }
+        }
+      ],
+      "site": {
+        "domain": "www.publisher.com",
+        "page": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+      }
+    },
+    "expectedMakeRequestsErrors": [
+      {
+        "value": "Video: Need at least one size to build request",
+        "comparison": "literal"
+      },
+      {
+        "value": "Error in preprocess of Imp, err: [Video: Need at least one size to build request]",
+        "comparison": "literal"
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/build-banner-object.json b/adapters/emx_digital/emx_digitaltest/supplemental/build-banner-object.json
index de1465e9..87418621 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/build-banner-object.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/build-banner-object.json
@@ -48,7 +48,13 @@
                     "secure": 0,
                     "tagid": "1234568",
                     "bidfloor": 1.25,
-                    "bidfloorcur": "USD"
+                    "bidfloorcur": "USD",
+                    "ext": {
+                        "bidder": {
+                            "tagid": "1234568",
+                            "bidfloor": "1.25"
+                        }
+                    }
                 }]
             }
         },
@@ -59,4 +65,4 @@
           }
         }
     }]
-}
\ No newline at end of file
+}
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/build-video-object.json b/adapters/emx_digital/emx_digitaltest/supplemental/build-video-object.json
new file mode 100644
index 00000000..a8cbc270
--- /dev/null
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/build-video-object.json
@@ -0,0 +1,69 @@
+{
+    "mockBidRequest": {
+        "id": "some_test_auction",
+        "imp": [{
+            "id": "some_test_ad",
+            "video":{
+                "mimes": [
+                  "video/mp4",
+                  "application/javascript"
+                ],
+                "protocols":[
+                   2,
+                   3,
+                   5,
+                   6
+                ],
+                "w":640,
+                "h":480
+             },
+            "ext": {
+                "bidder": {
+                    "tagid": "1234568",
+                    "bidfloor": "1.25"
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+            "body": {
+                "id": "some_test_auction",
+                "imp": [{
+                    "video":{
+                        "mimes": [
+                          "video/mp4",
+                          "application/javascript"
+                        ],
+                        "protocols":[
+                           2,
+                           3,
+                           5,
+                           6
+                        ],
+                        "w":640,
+                        "h":480
+                     },
+                    "id": "some_test_ad",
+                    "secure": 0,
+                    "tagid": "1234568",
+                    "bidfloor": 1.25,
+                    "bidfloorcur": "USD",
+                    "ext": {
+                        "bidder": {
+                            "tagid": "1234568",
+                            "bidfloor": "1.25"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+
+          }
+        }
+    }]
+}
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-no-bids.json b/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-no-bids.json
index b27f9866..d6842935 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-no-bids.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-no-bids.json
@@ -31,6 +31,11 @@
                   "h": 300
                 },
                 "secure": 0,
+                "ext": {
+                  "bidder": {
+                    "tagid": "98765"
+                  }
+                },
                 "tagid": "98765"
               }
             ]
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json b/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json
index 21492e74..1281db66 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json
@@ -27,6 +27,11 @@
                     },
                     "id": "some_test_ad",
                     "secure": 0,
+                    "ext": {
+                        "bidder": {
+                            "tagid": "654321"
+                        }
+                    },
                     "tagid": "654321"
                 }]
             }
@@ -60,4 +65,4 @@
             "comparison": "regex"
         }
     ]
-}
\ No newline at end of file
+}
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/no-imps-in-request.json b/adapters/emx_digital/emx_digitaltest/supplemental/no-imps-in-request.json
index 274a3422..9527e78e 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/no-imps-in-request.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/no-imps-in-request.json
@@ -15,4 +15,4 @@
           "comparison": "literal"
         }
       ]
-}
\ No newline at end of file
+}
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/server-error-code.json b/adapters/emx_digital/emx_digitaltest/supplemental/server-error-code.json
index a18ef316..e2652b36 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/server-error-code.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/server-error-code.json
@@ -31,6 +31,11 @@
                   "h": 300
                 },
                 "secure": 0,
+                "ext": {
+                  "bidder": {
+                    "tagid": "1234567"
+                  }
+                },
                 "tagid": "1234567"
               }
             ]
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/server-no-content.json b/adapters/emx_digital/emx_digitaltest/supplemental/server-no-content.json
index 67fdbc39..d75bee4f 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/server-no-content.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/server-no-content.json
@@ -1,44 +1,49 @@
 {
-    "mockBidRequest": {
-    "id": "some_test_auction",
-    "imp": [
-      {
-        "id": "test-imp-id",
-        "banner": {
-            "w": 300,
-            "h": 250
-        },
-        "ext": {
-          "bidder": {
-            "tagId": "1234567"
-          }
+  "mockBidRequest": {
+  "id": "some_test_auction",
+  "imp": [
+    {
+      "id": "test-imp-id",
+      "banner": {
+          "w": 300,
+          "h": 250
+      },
+      "ext": {
+        "bidder": {
+          "tagId": "1234567"
         }
       }
-    ]
-  },
+    }
+  ]
+},
 
-    "httpCalls": [
-      {
-        "expectedRequest": {
-          "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
-          "body": {
-            "id": "some_test_auction",
-            "imp": [
-              {
-                "id": "test-imp-id",
-                "banner": {
-                    "w": 300,
-                    "h": 250
-                },
-                "secure": 0,
-                "tagid": "1234567"
-              }
-            ]
-          }
-        },
-        "mockResponse": {
-          "status": 204
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160",
+        "body": {
+          "id": "some_test_auction",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                  "w": 300,
+                  "h": 250
+              },
+              "secure": 0,
+              "ext": {
+                "bidder": {
+                  "tagId": "1234567"
+                }
+              },
+              "tagid": "1234567"
+            }
+          ]
         }
+      },
+      "mockResponse": {
+        "status": 204
       }
-    ]
     }
+  ]
+}
diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/site-domain-and-url-correctly-parsed.json b/adapters/emx_digital/emx_digitaltest/supplemental/site-domain-and-url-correctly-parsed.json
index be9bdb94..e92ccea6 100644
--- a/adapters/emx_digital/emx_digitaltest/supplemental/site-domain-and-url-correctly-parsed.json
+++ b/adapters/emx_digital/emx_digitaltest/supplemental/site-domain-and-url-correctly-parsed.json
@@ -41,6 +41,11 @@
                     "w": 300,
                     "h": 600
                 },
+                "ext": {
+                  "bidder": {
+                    "tagid": "123456"
+                  }
+                },
                 "tagid": "123456",
                 "secure": 0
             }],
diff --git a/config/config_test.go b/config/config_test.go
index d2e80c7e..e11dac08 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -652,8 +652,7 @@ func TestValidateAccountsConfigRestrictions(t *testing.T) {
 	cfg.Accounts.Postgres.ConnectionInfo.Database = "accounts"
 
 	errs := cfg.validate()
-	assert.Len(t, errs, 2)
-	assert.Contains(t, errs, errors.New("accounts.http: retrieving accounts via http not available, use accounts.files"))
+	assert.Len(t, errs, 1)
 	assert.Contains(t, errs, errors.New("accounts.postgres: retrieving accounts via postgres not available, use accounts.files"))
 }
 
diff --git a/config/stored_requests_test.go b/config/stored_requests_test.go
index a3bd5a96..efa78816 100644
--- a/config/stored_requests_test.go
+++ b/config/stored_requests_test.go
@@ -1,9 +1,12 @@
 package config
 
 import (
+	"errors"
 	"strconv"
 	"strings"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 const sampleQueryTemplate = "SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in %REQUEST_ID_LIST% UNION ALL SELECT id, impData, 'imp' as type FROM stored_requests WHERE id in %IMP_ID_LIST%"
@@ -154,6 +157,108 @@ func TestInMemoryCacheValidationSingleCache(t *testing.T) {
 	}).validate(AccountDataType, nil))
 }
 
+func TestPostgresConfigValidation(t *testing.T) {
+	tests := []struct {
+		description            string
+		connectionStr          string
+		cacheInitQuery         string
+		cacheInitTimeout       int
+		cacheUpdateQuery       string
+		cacheUpdateRefreshRate int
+		cacheUpdateTimeout     int
+		existingErrors         []error
+		wantErrorCount         int
+	}{
+		{
+			description:   "No connection string",
+			connectionStr: "",
+		},
+		{
+			description:   "Connection string but no queries",
+			connectionStr: "some-connection-string",
+		},
+		{
+			description:      "Valid cache init query with non-zero timeout",
+			connectionStr:    "some-connection-string",
+			cacheInitQuery:   "SELECT * FROM table;",
+			cacheInitTimeout: 1,
+		},
+		{
+			description:      "Valid cache init query with zero timeout",
+			connectionStr:    "some-connection-string",
+			cacheInitQuery:   "SELECT * FROM table;",
+			cacheInitTimeout: 0,
+			wantErrorCount:   1,
+		},
+		{
+			description:      "Invalid cache init query contains wildcard",
+			connectionStr:    "some-connection-string",
+			cacheInitQuery:   "SELECT * FROM table WHERE $1",
+			cacheInitTimeout: 1,
+			wantErrorCount:   1,
+		},
+		{
+			description:            "Valid cache update query with non-zero timeout and refresh rate",
+			connectionStr:          "some-connection-string",
+			cacheUpdateQuery:       "SELECT * FROM table WHERE $1",
+			cacheUpdateRefreshRate: 1,
+			cacheUpdateTimeout:     1,
+		},
+		{
+			description:            "Valid cache update query with zero timeout and non-zero refresh rate",
+			connectionStr:          "some-connection-string",
+			cacheUpdateQuery:       "SELECT * FROM table WHERE $1",
+			cacheUpdateRefreshRate: 1,
+			cacheUpdateTimeout:     0,
+			wantErrorCount:         1,
+		},
+		{
+			description:            "Valid cache update query with non-zero timeout and zero refresh rate",
+			connectionStr:          "some-connection-string",
+			cacheUpdateQuery:       "SELECT * FROM table WHERE $1",
+			cacheUpdateRefreshRate: 0,
+			cacheUpdateTimeout:     1,
+			wantErrorCount:         1,
+		},
+		{
+			description:            "Invalid cache update query missing wildcard",
+			connectionStr:          "some-connection-string",
+			cacheUpdateQuery:       "SELECT * FROM table",
+			cacheUpdateRefreshRate: 1,
+			cacheUpdateTimeout:     1,
+			wantErrorCount:         1,
+		},
+		{
+			description:      "Multiple errors: valid queries missing timeouts and refresh rates plus existing error",
+			connectionStr:    "some-connection-string",
+			cacheInitQuery:   "SELECT * FROM table;",
+			cacheUpdateQuery: "SELECT * FROM table WHERE $1",
+			existingErrors:   []error{errors.New("existing error before calling validate")},
+			wantErrorCount:   4,
+		},
+	}
+
+	for _, tt := range tests {
+		pgConfig := &PostgresConfig{
+			ConnectionInfo: PostgresConnection{
+				Database: tt.connectionStr,
+			},
+			CacheInitialization: PostgresCacheInitializer{
+				Query:   tt.cacheInitQuery,
+				Timeout: tt.cacheInitTimeout,
+			},
+			PollUpdates: PostgresUpdatePolling{
+				Query:       tt.cacheUpdateQuery,
+				RefreshRate: tt.cacheUpdateRefreshRate,
+				Timeout:     tt.cacheUpdateTimeout,
+			},
+		}
+
+		errs := pgConfig.validate(RequestDataType, tt.existingErrors)
+		assert.Equal(t, tt.wantErrorCount, len(errs), tt.description)
+	}
+}
+
 func assertErrsExist(t *testing.T, err configErrors) {
 	t.Helper()
 	if len(err) == 0 {
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index be57271e..b8dd75b2 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -1232,7 +1232,7 @@ func TestBuildAmpObject(t *testing.T) {
 					},
 					AT:   1,
 					TMax: 500,
-					Ext:  json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":null},"vastxml":null},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true,"includebrandcategory":null,"includeformat":false,"durationrangesec":null}}}`),
+					Ext:  json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":null},"vastxml":null},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true,"includebrandcategory":null,"includeformat":false,"durationrangesec":null,"preferdeals":false}}}`),
 				},
 				AuctionResponse: &openrtb.BidResponse{
 					SeatBid: []openrtb.SeatBid{{
diff --git a/exchange/auction_test.go b/exchange/auction_test.go
index 9f24682b..cdd6a6ca 100644
--- a/exchange/auction_test.go
+++ b/exchange/auction_test.go
@@ -280,6 +280,225 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) {
 	}
 }
 
+func TestNewAuction(t *testing.T) {
+	bid1p077 := pbsOrtbBid{
+		bid: &openrtb.Bid{
+			ImpID: "imp1",
+			Price: 0.77,
+		},
+	}
+	bid1p123 := pbsOrtbBid{
+		bid: &openrtb.Bid{
+			ImpID: "imp1",
+			Price: 1.23,
+		},
+	}
+	bid1p230 := pbsOrtbBid{
+		bid: &openrtb.Bid{
+			ImpID: "imp1",
+			Price: 2.30,
+		},
+	}
+	bid1p088d := pbsOrtbBid{
+		bid: &openrtb.Bid{
+			ImpID:  "imp1",
+			Price:  0.88,
+			DealID: "SpecialDeal",
+		},
+	}
+	bid1p166d := pbsOrtbBid{
+		bid: &openrtb.Bid{
+			ImpID:  "imp1",
+			Price:  1.66,
+			DealID: "BigDeal",
+		},
+	}
+	bid2p123 := pbsOrtbBid{
+		bid: &openrtb.Bid{
+			ImpID: "imp2",
+			Price: 1.23,
+		},
+	}
+	bid2p144 := pbsOrtbBid{
+		bid: &openrtb.Bid{
+			ImpID: "imp2",
+			Price: 1.44,
+		},
+	}
+	tests := []struct {
+		description     string
+		seatBids        map[openrtb_ext.BidderName]*pbsOrtbSeatBid
+		numImps         int
+		preferDeals     bool
+		expectedAuction auction
+	}{
+		{
+			description: "Basic auction test",
+			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				"appnexus": {
+					bids: []*pbsOrtbBid{&bid1p123},
+				},
+				"rubicon": {
+					bids: []*pbsOrtbBid{&bid1p230},
+				},
+			},
+			numImps:     1,
+			preferDeals: false,
+			expectedAuction: auction{
+				winningBids: map[string]*pbsOrtbBid{
+					"imp1": &bid1p230,
+				},
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+					"imp1": {
+						"appnexus": &bid1p123,
+						"rubicon":  &bid1p230,
+					},
+				},
+			},
+		},
+		{
+			description: "Multi-imp auction",
+			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				"appnexus": {
+					bids: []*pbsOrtbBid{&bid1p230, &bid2p123},
+				},
+				"rubicon": {
+					bids: []*pbsOrtbBid{&bid1p077, &bid2p144},
+				},
+				"openx": {
+					bids: []*pbsOrtbBid{&bid1p123},
+				},
+			},
+			numImps:     2,
+			preferDeals: false,
+			expectedAuction: auction{
+				winningBids: map[string]*pbsOrtbBid{
+					"imp1": &bid1p230,
+					"imp2": &bid2p144,
+				},
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+					"imp1": {
+						"appnexus": &bid1p230,
+						"rubicon":  &bid1p077,
+						"openx":    &bid1p123,
+					},
+					"imp2": {
+						"appnexus": &bid2p123,
+						"rubicon":  &bid2p144,
+					},
+				},
+			},
+		},
+		{
+			description: "Basic auction with deals, no preference",
+			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				"appnexus": {
+					bids: []*pbsOrtbBid{&bid1p123},
+				},
+				"rubicon": {
+					bids: []*pbsOrtbBid{&bid1p088d},
+				},
+			},
+			numImps:     1,
+			preferDeals: false,
+			expectedAuction: auction{
+				winningBids: map[string]*pbsOrtbBid{
+					"imp1": &bid1p123,
+				},
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+					"imp1": {
+						"appnexus": &bid1p123,
+						"rubicon":  &bid1p088d,
+					},
+				},
+			},
+		},
+		{
+			description: "Basic auction with deals, prefer deals",
+			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				"appnexus": {
+					bids: []*pbsOrtbBid{&bid1p123},
+				},
+				"rubicon": {
+					bids: []*pbsOrtbBid{&bid1p088d},
+				},
+			},
+			numImps:     1,
+			preferDeals: true,
+			expectedAuction: auction{
+				winningBids: map[string]*pbsOrtbBid{
+					"imp1": &bid1p088d,
+				},
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+					"imp1": {
+						"appnexus": &bid1p123,
+						"rubicon":  &bid1p088d,
+					},
+				},
+			},
+		},
+		{
+			description: "Auction with 2 deals",
+			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				"appnexus": {
+					bids: []*pbsOrtbBid{&bid1p166d},
+				},
+				"rubicon": {
+					bids: []*pbsOrtbBid{&bid1p088d},
+				},
+			},
+			numImps:     1,
+			preferDeals: true,
+			expectedAuction: auction{
+				winningBids: map[string]*pbsOrtbBid{
+					"imp1": &bid1p166d,
+				},
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+					"imp1": {
+						"appnexus": &bid1p166d,
+						"rubicon":  &bid1p088d,
+					},
+				},
+			},
+		},
+		{
+			description: "Auction with 3 bids and 2 deals",
+			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				"appnexus": {
+					bids: []*pbsOrtbBid{&bid1p166d},
+				},
+				"rubicon": {
+					bids: []*pbsOrtbBid{&bid1p088d},
+				},
+				"openx": {
+					bids: []*pbsOrtbBid{&bid1p230},
+				},
+			},
+			numImps:     1,
+			preferDeals: true,
+			expectedAuction: auction{
+				winningBids: map[string]*pbsOrtbBid{
+					"imp1": &bid1p166d,
+				},
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+					"imp1": {
+						"appnexus": &bid1p166d,
+						"rubicon":  &bid1p088d,
+						"openx":    &bid1p230,
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		auc := newAuction(test.seatBids, test.numImps, test.preferDeals)
+
+		assert.Equal(t, test.expectedAuction, *auc, test.description)
+	}
+
+}
+
 type cacheSpec struct {
 	BidRequest                  openrtb.BidRequest              `json:"bidRequest"`
 	PbsBids                     []pbsBid                        `json:"pbsBids"`
diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go
index f6769916..0a763750 100644
--- a/pbsmetrics/go_metrics_test.go
+++ b/pbsmetrics/go_metrics_test.go
@@ -345,6 +345,151 @@ func TestRecordPrebidCacheRequestTimeWithNotSuccess(t *testing.T) {
 	assert.Equal(t, m.PrebidCacheRequestTimerError.Count(), int64(1))
 }
 
+func TestRecordStoredDataFetchTime(t *testing.T) {
+	tests := []struct {
+		description string
+		dataType    StoredDataType
+		fetchType   StoredDataFetchType
+	}{
+		{
+			description: "Update stored_account_fetch_time.all timer",
+			dataType:    AccountDataType,
+			fetchType:   FetchAll,
+		},
+		{
+			description: "Update stored_amp_fetch_time.all timer",
+			dataType:    AMPDataType,
+			fetchType:   FetchAll,
+		},
+		{
+			description: "Update stored_category_fetch_time.all timer",
+			dataType:    CategoryDataType,
+			fetchType:   FetchAll,
+		},
+		{
+			description: "Update stored_request_fetch_time.all timer",
+			dataType:    RequestDataType,
+			fetchType:   FetchAll,
+		},
+		{
+			description: "Update stored_video_fetch_time.all timer",
+			dataType:    VideoDataType,
+			fetchType:   FetchAll,
+		},
+		{
+			description: "Update stored_account_fetch_time.delta timer",
+			dataType:    AccountDataType,
+			fetchType:   FetchDelta,
+		},
+		{
+			description: "Update stored_amp_fetch_time.delta timer",
+			dataType:    AMPDataType,
+			fetchType:   FetchDelta,
+		},
+		{
+			description: "Update stored_category_fetch_time.delta timer",
+			dataType:    CategoryDataType,
+			fetchType:   FetchDelta,
+		},
+		{
+			description: "Update stored_request_fetch_time.delta timer",
+			dataType:    RequestDataType,
+			fetchType:   FetchDelta,
+		},
+		{
+			description: "Update stored_video_fetch_time.delta timer",
+			dataType:    VideoDataType,
+			fetchType:   FetchDelta,
+		},
+	}
+
+	for _, tt := range tests {
+		registry := metrics.NewRegistry()
+		m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true})
+		m.RecordStoredDataFetchTime(StoredDataLabels{
+			DataType:      tt.dataType,
+			DataFetchType: tt.fetchType,
+		}, time.Duration(500))
+
+		actualCount := m.StoredDataFetchTimer[tt.dataType][tt.fetchType].Count()
+		assert.Equal(t, int64(1), actualCount, tt.description)
+
+		actualDuration := m.StoredDataFetchTimer[tt.dataType][tt.fetchType].Sum()
+		assert.Equal(t, int64(500), actualDuration, tt.description)
+	}
+}
+
+func TestRecordStoredDataError(t *testing.T) {
+	tests := []struct {
+		description string
+		dataType    StoredDataType
+		errorType   StoredDataError
+	}{
+		{
+			description: "Increment stored_account_error.network meter",
+			dataType:    AccountDataType,
+			errorType:   StoredDataErrorNetwork,
+		},
+		{
+			description: "Increment stored_amp_error.network meter",
+			dataType:    AMPDataType,
+			errorType:   StoredDataErrorNetwork,
+		},
+		{
+			description: "Increment stored_category_error.network meter",
+			dataType:    CategoryDataType,
+			errorType:   StoredDataErrorNetwork,
+		},
+		{
+			description: "Increment stored_request_error.network meter",
+			dataType:    RequestDataType,
+			errorType:   StoredDataErrorNetwork,
+		},
+		{
+			description: "Increment stored_video_error.network meter",
+			dataType:    VideoDataType,
+			errorType:   StoredDataErrorNetwork,
+		},
+		{
+			description: "Increment stored_account_error.undefined meter",
+			dataType:    AccountDataType,
+			errorType:   StoredDataErrorUndefined,
+		},
+		{
+			description: "Increment stored_amp_error.undefined meter",
+			dataType:    AMPDataType,
+			errorType:   StoredDataErrorUndefined,
+		},
+		{
+			description: "Increment stored_category_error.undefined meter",
+			dataType:    CategoryDataType,
+			errorType:   StoredDataErrorUndefined,
+		},
+		{
+			description: "Increment stored_request_error.undefined meter",
+			dataType:    RequestDataType,
+			errorType:   StoredDataErrorUndefined,
+		},
+		{
+			description: "Increment stored_video_error.undefined meter",
+			dataType:    VideoDataType,
+			errorType:   StoredDataErrorUndefined,
+		},
+	}
+
+	for _, tt := range tests {
+		registry := metrics.NewRegistry()
+		m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true})
+		m.RecordStoredDataError(StoredDataLabels{
+			DataType: tt.dataType,
+			Error:    tt.errorType,
+		})
+
+		actualCount := m.StoredDataErrorMeter[tt.dataType][tt.errorType].Count()
+		assert.Equal(t, int64(1), actualCount, tt.description)
+	}
+}
+
 func TestRecordRequestPrivacy(t *testing.T) {
 	registry := metrics.NewRegistry()
 	m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true})
diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go
index b6153b16..46be7005 100644
--- a/pbsmetrics/prometheus/prometheus_test.go
+++ b/pbsmetrics/prometheus/prometheus_test.go
@@ -407,6 +407,193 @@ func TestRequestTimeMetric(t *testing.T) {
 	}
 }
 
+func TestRecordStoredDataFetchTime(t *testing.T) {
+	tests := []struct {
+		description string
+		dataType    pbsmetrics.StoredDataType
+		fetchType   pbsmetrics.StoredDataFetchType
+	}{
+		{
+			description: "Update stored account histogram with all label",
+			dataType:    pbsmetrics.AccountDataType,
+			fetchType:   pbsmetrics.FetchAll,
+		},
+		{
+			description: "Update stored AMP histogram with all label",
+			dataType:    pbsmetrics.AMPDataType,
+			fetchType:   pbsmetrics.FetchAll,
+		},
+		{
+			description: "Update stored category histogram with all label",
+			dataType:    pbsmetrics.CategoryDataType,
+			fetchType:   pbsmetrics.FetchAll,
+		},
+		{
+			description: "Update stored request histogram with all label",
+			dataType:    pbsmetrics.RequestDataType,
+			fetchType:   pbsmetrics.FetchAll,
+		},
+		{
+			description: "Update stored video histogram with all label",
+			dataType:    pbsmetrics.VideoDataType,
+			fetchType:   pbsmetrics.FetchAll,
+		},
+		{
+			description: "Update stored account histogram with delta label",
+			dataType:    pbsmetrics.AccountDataType,
+			fetchType:   pbsmetrics.FetchDelta,
+		},
+		{
+			description: "Update stored AMP histogram with delta label",
+			dataType:    pbsmetrics.AMPDataType,
+			fetchType:   pbsmetrics.FetchDelta,
+		},
+		{
+			description: "Update stored category histogram with delta label",
+			dataType:    pbsmetrics.CategoryDataType,
+			fetchType:   pbsmetrics.FetchDelta,
+		},
+		{
+			description: "Update stored request histogram with delta label",
+			dataType:    pbsmetrics.RequestDataType,
+			fetchType:   pbsmetrics.FetchDelta,
+		},
+		{
+			description: "Update stored video histogram with delta label",
+			dataType:    pbsmetrics.VideoDataType,
+			fetchType:   pbsmetrics.FetchDelta,
+		},
+	}
+
+	for _, tt := range tests {
+		m := createMetricsForTesting()
+
+		fetchTime := time.Duration(0.5 * float64(time.Second))
+		m.RecordStoredDataFetchTime(pbsmetrics.StoredDataLabels{
+			DataType:      tt.dataType,
+			DataFetchType: tt.fetchType,
+		}, fetchTime)
+
+		var metricsTimer *prometheus.HistogramVec
+		switch tt.dataType {
+		case pbsmetrics.AccountDataType:
+			metricsTimer = m.storedAccountFetchTimer
+		case pbsmetrics.AMPDataType:
+			metricsTimer = m.storedAMPFetchTimer
+		case pbsmetrics.CategoryDataType:
+			metricsTimer = m.storedCategoryFetchTimer
+		case pbsmetrics.RequestDataType:
+			metricsTimer = m.storedRequestFetchTimer
+		case pbsmetrics.VideoDataType:
+			metricsTimer = m.storedVideoFetchTimer
+		}
+
+		result := getHistogramFromHistogramVec(
+			metricsTimer,
+			storedDataFetchTypeLabel,
+			string(tt.fetchType))
+		assertHistogram(t, tt.description, result, 1, 0.5)
+	}
+}
+
+func TestRecordStoredDataError(t *testing.T) {
+	tests := []struct {
+		description string
+		dataType    pbsmetrics.StoredDataType
+		errorType   pbsmetrics.StoredDataError
+		metricName  string
+	}{
+		{
+			description: "Update stored_account_errors counter with network label",
+			dataType:    pbsmetrics.AccountDataType,
+			errorType:   pbsmetrics.StoredDataErrorNetwork,
+			metricName:  "stored_account_errors",
+		},
+		{
+			description: "Update stored_amp_errors counter with network label",
+			dataType:    pbsmetrics.AMPDataType,
+			errorType:   pbsmetrics.StoredDataErrorNetwork,
+			metricName:  "stored_amp_errors",
+		},
+		{
+			description: "Update stored_category_errors counter with network label",
+			dataType:    pbsmetrics.CategoryDataType,
+			errorType:   pbsmetrics.StoredDataErrorNetwork,
+			metricName:  "stored_category_errors",
+		},
+		{
+			description: "Update stored_request_errors counter with network label",
+			dataType:    pbsmetrics.RequestDataType,
+			errorType:   pbsmetrics.StoredDataErrorNetwork,
+			metricName:  "stored_request_errors",
+		},
+		{
+			description: "Update stored_video_errors counter with network label",
+			dataType:    pbsmetrics.VideoDataType,
+			errorType:   pbsmetrics.StoredDataErrorNetwork,
+			metricName:  "stored_video_errors",
+		},
+		{
+			description: "Update stored_account_errors counter with undefined label",
+			dataType:    pbsmetrics.AccountDataType,
+			errorType:   pbsmetrics.StoredDataErrorUndefined,
+			metricName:  "stored_account_errors",
+		},
+		{
+			description: "Update stored_amp_errors counter with undefined label",
+			dataType:    pbsmetrics.AMPDataType,
+			errorType:   pbsmetrics.StoredDataErrorUndefined,
+			metricName:  "stored_amp_errors",
+		},
+		{
+			description: "Update stored_category_errors counter with undefined label",
+			dataType:    pbsmetrics.CategoryDataType,
+			errorType:   pbsmetrics.StoredDataErrorUndefined,
+			metricName:  "stored_category_errors",
+		},
+		{
+			description: "Update stored_request_errors counter with undefined label",
+			dataType:    pbsmetrics.RequestDataType,
+			errorType:   pbsmetrics.StoredDataErrorUndefined,
+			metricName:  "stored_request_errors",
+		},
+		{
+			description: "Update stored_video_errors counter with undefined label",
+			dataType:    pbsmetrics.VideoDataType,
+			errorType:   pbsmetrics.StoredDataErrorUndefined,
+			metricName:  "stored_video_errors",
+		},
+	}
+
+	for _, tt := range tests {
+		m := createMetricsForTesting()
+		m.RecordStoredDataError(pbsmetrics.StoredDataLabels{
+			DataType: tt.dataType,
+			Error:    tt.errorType,
+		})
+
+		var metricsCounter *prometheus.CounterVec
+		switch tt.dataType {
+		case pbsmetrics.AccountDataType:
+			metricsCounter = m.storedAccountErrors
+		case pbsmetrics.AMPDataType:
+			metricsCounter = m.storedAMPErrors
+		case pbsmetrics.CategoryDataType:
+			metricsCounter = m.storedCategoryErrors
+		case pbsmetrics.RequestDataType:
+			metricsCounter = m.storedRequestErrors
+		case pbsmetrics.VideoDataType:
+			metricsCounter = m.storedVideoErrors
+		}
+
+		assertCounterVecValue(t, tt.description, tt.metricName, metricsCounter,
+			1,
+			prometheus.Labels{
+				storedDataErrorLabel: string(tt.errorType),
+			})
+	}
+}
+
 func TestAdapterBidReceivedMetric(t *testing.T) {
 	adapterName := "anyName"
 	performTest := func(m *Metrics, hasAdm bool) {
@@ -1240,6 +1427,8 @@ func getHistogramFromHistogramVecByTwoKeys(histogram *prometheus.HistogramVec, l
 				valInd := ind
 				if ind == 1 {
 					valInd = 0
+				} else {
+					valInd = 1
 				}
 				if m.Label[valInd].GetName() == label2Key && m.Label[valInd].GetValue() == label2Value {
 					result = *m.GetHistogram()
diff --git a/stored_requests/backends/http_fetcher/fetcher_test.go b/stored_requests/backends/http_fetcher/fetcher_test.go
index dc4076fd..30933181 100644
--- a/stored_requests/backends/http_fetcher/fetcher_test.go
+++ b/stored_requests/backends/http_fetcher/fetcher_test.go
@@ -9,6 +9,9 @@ import (
 	"net/http/httptest"
 	"strings"
 	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestSingleReq(t *testing.T) {
@@ -16,9 +19,9 @@ func TestSingleReq(t *testing.T) {
 	defer close()
 
 	reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1"}, nil)
+	assert.Empty(t, errs, "Unexpected errors fetching known requests")
 	assertMapKeys(t, reqData, "req-1")
-	assertMapKeys(t, impData)
-	assertErrLength(t, errs, 0)
+	assert.Empty(t, impData, "Unexpected imps returned fetching just requests")
 }
 
 func TestSeveralReqs(t *testing.T) {
@@ -26,9 +29,9 @@ func TestSeveralReqs(t *testing.T) {
 	defer close()
 
 	reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1", "req-2"}, nil)
+	assert.Empty(t, errs, "Unexpected errors fetching known requests")
 	assertMapKeys(t, reqData, "req-1", "req-2")
-	assertMapKeys(t, impData)
-	assertErrLength(t, errs, 0)
+	assert.Empty(t, impData, "Unexpected imps returned fetching just requests")
 }
 
 func TestSingleImp(t *testing.T) {
@@ -36,9 +39,9 @@ func TestSingleImp(t *testing.T) {
 	defer close()
 
 	reqData, impData, errs := fetcher.FetchRequests(context.Background(), nil, []string{"imp-1"})
-	assertMapKeys(t, reqData)
+	assert.Empty(t, errs, "Unexpected errors fetching known imps")
+	assert.Empty(t, reqData, "Unexpected requests returned fetching just imps")
 	assertMapKeys(t, impData, "imp-1")
-	assertErrLength(t, errs, 0)
 }
 
 func TestSeveralImps(t *testing.T) {
@@ -46,9 +49,9 @@ func TestSeveralImps(t *testing.T) {
 	defer close()
 
 	reqData, impData, errs := fetcher.FetchRequests(context.Background(), nil, []string{"imp-1", "imp-2"})
-	assertMapKeys(t, reqData)
+	assert.Empty(t, errs, "Unexpected errors fetching known imps")
+	assert.Empty(t, reqData, "Unexpected requests returned fetching just imps")
 	assertMapKeys(t, impData, "imp-1", "imp-2")
-	assertErrLength(t, errs, 0)
 }
 
 func TestReqsAndImps(t *testing.T) {
@@ -56,9 +59,9 @@ func TestReqsAndImps(t *testing.T) {
 	defer close()
 
 	reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1"}, []string{"imp-1"})
+	assert.Empty(t, errs, "Unexpected errors fetching known reqs and imps")
 	assertMapKeys(t, reqData, "req-1")
 	assertMapKeys(t, impData, "imp-1")
-	assertErrLength(t, errs, 0)
 }
 
 func TestMissingValues(t *testing.T) {
@@ -66,9 +69,94 @@ func TestMissingValues(t *testing.T) {
 	defer close()
 
 	reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1", "req-2"}, []string{"imp-1"})
-	assertMapKeys(t, reqData)
-	assertMapKeys(t, impData)
-	assertErrLength(t, errs, 3)
+	assert.Empty(t, reqData, "Fetching unknown reqs should return no reqs")
+	assert.Empty(t, impData, "Fetching unknown imps should return no imps")
+	assert.Len(t, errs, 3, "Fetching 3 unknown reqs+imps should return 3 errors")
+}
+
+func TestFetchAccounts(t *testing.T) {
+	fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"})
+	defer close()
+
+	accData, errs := fetcher.FetchAccounts(context.Background(), []string{"acc-1", "acc-2"})
+	assert.Empty(t, errs, "Unexpected error fetching known accounts")
+	assertMapKeys(t, accData, "acc-1", "acc-2")
+}
+
+func TestFetchAccountsNoData(t *testing.T) {
+	fetcher, close := newFetcherBrokenBackend()
+	defer close()
+
+	accData, errs := fetcher.FetchAccounts(context.Background(), []string{"req-1"})
+	assert.Len(t, errs, 1, "Fetching unknown account should have returned an error")
+	assert.Nil(t, accData, "Fetching unknown account should return nil account map")
+}
+
+func TestFetchAccountsBadJSON(t *testing.T) {
+	fetcher, close := newFetcherBadJSON()
+	defer close()
+
+	accData, errs := fetcher.FetchAccounts(context.Background(), []string{"req-1"})
+	assert.Len(t, errs, 1, "Fetching account with broken json should have returned an error")
+	assert.Nil(t, accData, "Fetching account with broken json should return nil account map")
+}
+
+func TestFetchAccountsNoIDsProvided(t *testing.T) {
+	fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"})
+	defer close()
+
+	accData, errs := fetcher.FetchAccounts(nil, []string{})
+	assert.Empty(t, errs, "Unexpected error fetching empty account list")
+	assert.Nil(t, accData, "Fetching empty account list should return nil")
+}
+
+// Force build request failure by not providing a context
+func TestFetchAccountsFailedBuildRequest(t *testing.T) {
+	fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"})
+	defer close()
+
+	accData, errs := fetcher.FetchAccounts(nil, []string{"acc-1"})
+	assert.Len(t, errs, 1, "Fetching accounts without context should result in error ")
+	assert.Nil(t, accData, "Fetching accounts without context should return nil")
+}
+
+// Force http error via request timeout
+func TestFetchAccountsContextTimeout(t *testing.T) {
+	fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"})
+	defer close()
+
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(0))
+	defer cancel()
+	accData, errs := fetcher.FetchAccounts(ctx, []string{"acc-1"})
+	assert.Len(t, errs, 1, "Expected context timeout error")
+	assert.Nil(t, accData, "Unexpected account data returned instead of timeout")
+}
+
+func TestFetchAccount(t *testing.T) {
+	fetcher, close := newTestAccountFetcher(t, []string{"acc-1"})
+	defer close()
+
+	account, errs := fetcher.FetchAccount(context.Background(), "acc-1")
+	assert.Empty(t, errs, "Unexpected error fetching existing account")
+	assert.JSONEq(t, `"acc-1"`, string(account), "Unexpected account data fetching existing account")
+}
+
+func TestFetchAccountNoData(t *testing.T) {
+	fetcher, close := newFetcherBrokenBackend()
+	defer close()
+
+	unknownAccount, errs := fetcher.FetchAccount(context.Background(), "unknown-acc")
+	assert.NotEmpty(t, errs, "Retrieving unknown account should return error")
+	assert.Nil(t, unknownAccount, "Retrieving unknown account should return nil json.RawMessage")
+}
+
+func TestFetchAccountNoIDProvided(t *testing.T) {
+	fetcher, close := newTestAccountFetcher(t, nil)
+	defer close()
+
+	account, errs := fetcher.FetchAccount(context.Background(), "")
+	assert.Len(t, errs, 1, "Fetching account with empty id should error")
+	assert.Nil(t, account, "Fetching account with empty id should return nil")
 }
 
 func TestErrResponse(t *testing.T) {
@@ -77,7 +165,7 @@ func TestErrResponse(t *testing.T) {
 	reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1"}, []string{"imp-1"})
 	assertMapKeys(t, reqData)
 	assertMapKeys(t, impData)
-	assertErrLength(t, errs, 1)
+	assert.Len(t, errs, 1)
 }
 
 func assertSameContents(t *testing.T, expected map[string]json.RawMessage, actual map[string]json.RawMessage) {
@@ -124,6 +212,14 @@ func newFetcherBrokenBackend() (fetcher *HttpFetcher, closer func()) {
 	return NewFetcher(server.Client(), server.URL), server.Close
 }
 
+func newFetcherBadJSON() (fetcher *HttpFetcher, closer func()) {
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte(`broken JSON`))
+	}
+	server := httptest.NewServer(http.HandlerFunc(handler))
+	return NewFetcher(server.Client(), server.URL), server.Close
+}
+
 func newEmptyFetcher(t *testing.T, expectReqIDs []string, expectImpIDs []string) (fetcher *HttpFetcher, closer func()) {
 	handler := newHandler(t, expectReqIDs, expectImpIDs, jsonifyToNull)
 	server := httptest.NewServer(http.HandlerFunc(handler))
@@ -139,12 +235,12 @@ func newTestFetcher(t *testing.T, expectReqIDs []string, expectImpIDs []string)
 func newHandler(t *testing.T, expectReqIDs []string, expectImpIDs []string, jsonifier func(string) json.RawMessage) func(w http.ResponseWriter, r *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		query := r.URL.Query()
-		assertMatches(t, query.Get("request-ids"), expectReqIDs)
-		assertMatches(t, query.Get("imp-ids"), expectImpIDs)
-
 		gotReqIDs := richSplit(query.Get("request-ids"))
 		gotImpIDs := richSplit(query.Get("imp-ids"))
 
+		assertMatches(t, gotReqIDs, expectReqIDs)
+		assertMatches(t, gotImpIDs, expectImpIDs)
+
 		reqIDResponse := make(map[string]json.RawMessage, len(gotReqIDs))
 		impIDResponse := make(map[string]json.RawMessage, len(gotImpIDs))
 
@@ -174,10 +270,43 @@ func newHandler(t *testing.T, expectReqIDs []string, expectImpIDs []string, json
 	}
 }
 
-func assertMatches(t *testing.T, query string, expected []string) {
+func newTestAccountFetcher(t *testing.T, expectAccIDs []string) (fetcher *HttpFetcher, closer func()) {
+	handler := newAccountHandler(t, expectAccIDs, jsonifyID)
+	server := httptest.NewServer(http.HandlerFunc(handler))
+	return NewFetcher(server.Client(), server.URL), server.Close
+}
+
+func newAccountHandler(t *testing.T, expectAccIDs []string, jsonifier func(string) json.RawMessage) func(w http.ResponseWriter, r *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		query := r.URL.Query()
+		gotAccIDs := richSplit(query.Get("account-ids"))
+
+		assertMatches(t, gotAccIDs, expectAccIDs)
+
+		accIDResponse := make(map[string]json.RawMessage, len(gotAccIDs))
+
+		for _, accID := range gotAccIDs {
+			if accID != "" {
+				accIDResponse[accID] = jsonifier(accID)
+			}
+		}
+
+		respObj := accountsResponseContract{
+			Accounts: accIDResponse,
+		}
+
+		if respBytes, err := json.Marshal(respObj); err != nil {
+			t.Errorf("failed to marshal responseContract in test:  %v", err)
+			w.WriteHeader(http.StatusInternalServerError)
+		} else {
+			w.Write(respBytes)
+		}
+	}
+}
+
+func assertMatches(t *testing.T, queryVals []string, expected []string) {
 	t.Helper()
 
-	queryVals := richSplit(query)
 	if len(queryVals) == 1 && queryVals[0] == "" {
 		if len(expected) != 0 {
 			t.Errorf("Expected no query vals, but got %v", queryVals)
@@ -250,11 +379,3 @@ func assertMapKeys(t *testing.T, m map[string]json.RawMessage, keys ...string) {
 		}
 	}
 }
-
-func assertErrLength(t *testing.T, errs []error, expected int) {
-	t.Helper()
-
-	if len(errs) != expected {
-		t.Errorf("Expected %d errors. Got: %v", expected, errs)
-	}
-}
diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go
index f225f74b..712fef32 100644
--- a/stored_requests/config/config_test.go
+++ b/stored_requests/config/config_test.go
@@ -14,11 +14,13 @@ import (
 	sqlmock "github.com/DATA-DOG/go-sqlmock"
 	"github.com/julienschmidt/httprouter"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/pbsmetrics"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
 	"github.com/prebid/prebid-server/stored_requests/backends/http_fetcher"
 	"github.com/prebid/prebid-server/stored_requests/events"
 	httpEvents "github.com/prebid/prebid-server/stored_requests/events/http"
+	"github.com/stretchr/testify/mock"
 )
 
 func typedConfig(dataType config.DataType, sr *config.StoredRequests) *config.StoredRequests {
@@ -76,7 +78,10 @@ func TestNewHTTPEvents(t *testing.T) {
 			Timeout:     1000,
 		},
 	}
-	evProducers := newEventProducers(cfg, server1.Client(), nil, nil)
+
+	metricsMock := &pbsmetrics.MetricsEngineMock{}
+
+	evProducers := newEventProducers(cfg, server1.Client(), nil, metricsMock, nil)
 	assertSliceLength(t, evProducers, 1)
 	assertHttpWithURL(t, evProducers[0], server1.URL)
 }
@@ -114,6 +119,10 @@ func TestNewInMemoryAccountCache(t *testing.T) {
 }
 
 func TestNewPostgresEventProducers(t *testing.T) {
+	metricsMock := &pbsmetrics.MetricsEngineMock{}
+	metricsMock.Mock.On("RecordStoredDataFetchTime", mock.Anything, mock.Anything).Return()
+	metricsMock.Mock.On("RecordStoredDataError", mock.Anything).Return()
+
 	cfg := &config.StoredRequests{
 		Postgres: config.PostgresConfig{
 			CacheInitialization: config.PostgresCacheInitializer{
@@ -134,10 +143,11 @@ func TestNewPostgresEventProducers(t *testing.T) {
 	}
 	mock.ExpectQuery("^" + regexp.QuoteMeta(cfg.Postgres.CacheInitialization.Query) + "$").WillReturnError(errors.New("Query failed"))
 
-	evProducers := newEventProducers(cfg, client, db, nil)
+	evProducers := newEventProducers(cfg, client, db, metricsMock, nil)
 	assertProducerLength(t, evProducers, 1)
 
 	assertExpectationsMet(t, mock)
+	metricsMock.AssertExpectations(t)
 }
 
 func TestNewEventsAPI(t *testing.T) {
diff --git a/stored_requests/events/postgres/database_test.go b/stored_requests/events/postgres/database_test.go
index da2e0868..4471ad41 100644
--- a/stored_requests/events/postgres/database_test.go
+++ b/stored_requests/events/postgres/database_test.go
@@ -7,8 +7,11 @@ import (
 	"testing"
 	"time"
 
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/pbsmetrics"
 	"github.com/prebid/prebid-server/stored_requests/events"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 
 	sqlmock "github.com/DATA-DOG/go-sqlmock"
 )
@@ -88,13 +91,21 @@ func TestFetchAllSuccess(t *testing.T) {
 	}
 
 	for _, tt := range tests {
-		db, mock, _ := sqlmock.New()
-		mock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
+		db, dbMock, _ := sqlmock.New()
+		dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
+
+		metricsMock := &pbsmetrics.MetricsEngineMock{}
+		metricsMock.Mock.On("RecordStoredDataFetchTime", pbsmetrics.StoredDataLabels{
+			DataType:      pbsmetrics.RequestDataType,
+			DataFetchType: pbsmetrics.FetchAll,
+		}, mock.Anything).Return()
 
 		eventProducer := NewPostgresEventProducer(PostgresEventProducerConfig{
 			DB:               db,
+			RequestType:      config.RequestDataType,
 			CacheInitTimeout: 100 * time.Millisecond,
 			CacheInitQuery:   fakeQuery,
+			MetricsEngine:    metricsMock,
 		})
 		eventProducer.time = &FakeTime{time: tt.giveFakeTime}
 		err := eventProducer.Run()
@@ -119,74 +130,76 @@ func TestFetchAllSuccess(t *testing.T) {
 		assert.Equal(t, tt.wantSavedImps, saves.Imps, tt.description)
 		assert.Equal(t, tt.wantInvalidatedReqs, invalidations.Requests, tt.description)
 		assert.Equal(t, tt.wantInvalidatedImps, invalidations.Imps, tt.description)
+
+		metricsMock.AssertExpectations(t)
 	}
 }
 
 func TestFetchAllErrors(t *testing.T) {
 	tests := []struct {
-		description         string
-		giveFakeTime        time.Time
-		giveMockRows        *sqlmock.Rows
-		wantReturnedError   bool
-		wantLastUpdate      time.Time
-		wantSavedReqs       map[string]json.RawMessage
-		wantSavedImps       map[string]json.RawMessage
-		wantInvalidatedReqs []string
-		wantInvalidatedImps []string
+		description       string
+		giveFakeTime      time.Time
+		giveTimeoutMS     int
+		giveMockRows      *sqlmock.Rows
+		wantRecordedError pbsmetrics.StoredDataError
+		wantLastUpdate    time.Time
 	}{
+		{
+			description:       "fetch all timeout",
+			giveFakeTime:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
+			giveMockRows:      nil,
+			wantRecordedError: pbsmetrics.StoredDataErrorNetwork,
+			wantLastUpdate:    time.Time{},
+		},
 		{
 			description:       "fetch all query error",
 			giveFakeTime:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
+			giveTimeoutMS:     100,
 			giveMockRows:      nil,
-			wantReturnedError: true,
+			wantRecordedError: pbsmetrics.StoredDataErrorUndefined,
 			wantLastUpdate:    time.Time{},
 		},
 		{
-			description:  "fetch all row error",
-			giveFakeTime: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
+			description:   "fetch all row error",
+			giveFakeTime:  time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
+			giveTimeoutMS: 100,
 			giveMockRows: sqlmock.NewRows([]string{"id", "data", "dataType"}).
 				AddRow("stored-req-id", "true", "request").
 				RowError(0, errors.New("Some row error.")),
-			wantReturnedError: true,
+			wantRecordedError: pbsmetrics.StoredDataErrorUndefined,
 			wantLastUpdate:    time.Time{},
 		},
-		{
-			description:  "fetch all close error",
-			giveFakeTime: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
-			giveMockRows: sqlmock.NewRows([]string{"id", "data", "dataType"}).
-				AddRow("req-1", "true", "request").
-				AddRow("imp-1", "true", "imp").
-				AddRow("req-2", "", "request").
-				AddRow("imp-2", "", "imp").
-				CloseError(errors.New("Some close error.")),
-			wantReturnedError: false,
-			wantLastUpdate:    time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
-			wantSavedReqs:     map[string]json.RawMessage{"req-1": json.RawMessage(`true`)},
-			wantSavedImps:     map[string]json.RawMessage{"imp-1": json.RawMessage(`true`)},
-		},
 	}
 
 	for _, tt := range tests {
-		db, mock, _ := sqlmock.New()
+		db, dbMock, _ := sqlmock.New()
 		if tt.giveMockRows == nil {
-			mock.ExpectQuery(fakeQueryRegex()).WillReturnError(errors.New("Query failed."))
+			dbMock.ExpectQuery(fakeQueryRegex()).WillReturnError(errors.New("Query failed."))
 		} else {
-			mock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
+			dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
 		}
 
+		metricsMock := &pbsmetrics.MetricsEngineMock{}
+		metricsMock.Mock.On("RecordStoredDataFetchTime", pbsmetrics.StoredDataLabels{
+			DataType:      pbsmetrics.RequestDataType,
+			DataFetchType: pbsmetrics.FetchAll,
+		}, mock.Anything).Return()
+		metricsMock.Mock.On("RecordStoredDataError", pbsmetrics.StoredDataLabels{
+			DataType: pbsmetrics.RequestDataType,
+			Error:    tt.wantRecordedError,
+		}).Return()
+
 		eventProducer := NewPostgresEventProducer(PostgresEventProducerConfig{
 			DB:               db,
-			CacheInitTimeout: 100 * time.Millisecond,
+			RequestType:      config.RequestDataType,
+			CacheInitTimeout: time.Duration(tt.giveTimeoutMS) * time.Millisecond,
 			CacheInitQuery:   fakeQuery,
+			MetricsEngine:    metricsMock,
 		})
 		eventProducer.time = &FakeTime{time: tt.giveFakeTime}
 		err := eventProducer.Run()
 
-		if tt.wantReturnedError {
-			assert.NotNil(t, err, tt.description)
-		} else {
-			assert.Nil(t, err, tt.description)
-		}
+		assert.NotNil(t, err, tt.description)
 		assert.Equal(t, tt.wantLastUpdate, eventProducer.lastUpdate, tt.description)
 
 		var saves events.Save
@@ -202,10 +215,12 @@ func TestFetchAllErrors(t *testing.T) {
 		case <-time.After(10 * time.Millisecond):
 		}
 
-		assert.Equal(t, tt.wantSavedReqs, saves.Requests, tt.description)
-		assert.Equal(t, tt.wantSavedImps, saves.Imps, tt.description)
-		assert.Equal(t, tt.wantInvalidatedReqs, invalidations.Requests, tt.description)
-		assert.Equal(t, tt.wantInvalidatedImps, invalidations.Imps, tt.description)
+		assert.Nil(t, saves.Requests, tt.description)
+		assert.Nil(t, saves.Imps, tt.description)
+		assert.Nil(t, invalidations.Requests, tt.description)
+		assert.Nil(t, invalidations.Requests, tt.description)
+
+		metricsMock.AssertExpectations(t)
 	}
 }
 
@@ -289,13 +304,21 @@ func TestFetchDeltaSuccess(t *testing.T) {
 	}
 
 	for _, tt := range tests {
-		db, mock, _ := sqlmock.New()
-		mock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
+		db, dbMock, _ := sqlmock.New()
+		dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
+
+		metricsMock := &pbsmetrics.MetricsEngineMock{}
+		metricsMock.Mock.On("RecordStoredDataFetchTime", pbsmetrics.StoredDataLabels{
+			DataType:      pbsmetrics.RequestDataType,
+			DataFetchType: pbsmetrics.FetchDelta,
+		}, mock.Anything).Return()
 
 		eventProducer := NewPostgresEventProducer(PostgresEventProducerConfig{
 			DB:                 db,
+			RequestType:        config.RequestDataType,
 			CacheUpdateTimeout: 100 * time.Millisecond,
 			CacheUpdateQuery:   fakeQuery,
+			MetricsEngine:      metricsMock,
 		})
 		eventProducer.lastUpdate = time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC)
 		eventProducer.time = &FakeTime{time: tt.giveFakeTime}
@@ -321,81 +344,81 @@ func TestFetchDeltaSuccess(t *testing.T) {
 		assert.Equal(t, tt.wantSavedImps, saves.Imps, tt.description)
 		assert.Equal(t, tt.wantInvalidatedReqs, invalidations.Requests, tt.description)
 		assert.Equal(t, tt.wantInvalidatedImps, invalidations.Imps, tt.description)
+
+		metricsMock.AssertExpectations(t)
 	}
 }
 
 func TestFetchDeltaErrors(t *testing.T) {
 	tests := []struct {
-		description         string
-		giveFakeTime        time.Time
-		giveLastUpdate      time.Time
-		giveMockRows        *sqlmock.Rows
-		wantReturnedError   bool
-		wantLastUpdate      time.Time
-		wantSavedReqs       map[string]json.RawMessage
-		wantSavedImps       map[string]json.RawMessage
-		wantInvalidatedReqs []string
-		wantInvalidatedImps []string
+		description       string
+		giveFakeTime      time.Time
+		giveTimeoutMS     int
+		giveLastUpdate    time.Time
+		giveMockRows      *sqlmock.Rows
+		wantRecordedError pbsmetrics.StoredDataError
+		wantLastUpdate    time.Time
 	}{
+		{
+			description:       "fetch delta timeout",
+			giveFakeTime:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
+			giveLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
+			giveMockRows:      nil,
+			wantRecordedError: pbsmetrics.StoredDataErrorNetwork,
+			wantLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
+		},
 		{
 			description:       "fetch delta query error",
 			giveFakeTime:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
+			giveTimeoutMS:     100,
 			giveLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
 			giveMockRows:      nil,
-			wantReturnedError: true,
+			wantRecordedError: pbsmetrics.StoredDataErrorUndefined,
 			wantLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
 		},
 		{
-			description:    "fetch all row error",
+			description:    "fetch delta row error",
 			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
+			giveTimeoutMS:  100,
 			giveLastUpdate: time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
 			giveMockRows: sqlmock.NewRows([]string{"id", "data", "dataType"}).
 				AddRow("stored-req-id", "true", "request").
 				RowError(0, errors.New("Some row error.")),
-			wantReturnedError: true,
+			wantRecordedError: pbsmetrics.StoredDataErrorUndefined,
 			wantLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
 		},
-		{
-			description:    "fetch all close error",
-			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
-			giveLastUpdate: time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
-			giveMockRows: sqlmock.NewRows([]string{"id", "data", "dataType"}).
-				AddRow("req-1", "true", "request").
-				AddRow("imp-1", "true", "imp").
-				AddRow("req-2", "", "request").
-				AddRow("imp-2", "", "imp").
-				CloseError(errors.New("Some close error.")),
-			wantReturnedError:   false,
-			wantLastUpdate:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
-			wantSavedReqs:       map[string]json.RawMessage{"req-1": json.RawMessage(`true`)},
-			wantSavedImps:       map[string]json.RawMessage{"imp-1": json.RawMessage(`true`)},
-			wantInvalidatedReqs: []string{"req-2"},
-			wantInvalidatedImps: []string{"imp-2"},
-		},
 	}
 
 	for _, tt := range tests {
-		db, mock, _ := sqlmock.New()
+		db, dbMock, _ := sqlmock.New()
 		if tt.giveMockRows == nil {
-			mock.ExpectQuery(fakeQueryRegex()).WillReturnError(errors.New("Query failed."))
+			dbMock.ExpectQuery(fakeQueryRegex()).WillReturnError(errors.New("Query failed."))
 		} else {
-			mock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
+			dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
 		}
 
+		metricsMock := &pbsmetrics.MetricsEngineMock{}
+		metricsMock.Mock.On("RecordStoredDataFetchTime", pbsmetrics.StoredDataLabels{
+			DataType:      pbsmetrics.RequestDataType,
+			DataFetchType: pbsmetrics.FetchDelta,
+		}, mock.Anything).Return()
+		metricsMock.Mock.On("RecordStoredDataError", pbsmetrics.StoredDataLabels{
+			DataType: pbsmetrics.RequestDataType,
+			Error:    tt.wantRecordedError,
+		}).Return()
+
 		eventProducer := NewPostgresEventProducer(PostgresEventProducerConfig{
 			DB:                 db,
-			CacheUpdateTimeout: 100 * time.Millisecond,
+			RequestType:        config.RequestDataType,
+			CacheUpdateTimeout: time.Duration(tt.giveTimeoutMS) * time.Millisecond,
 			CacheUpdateQuery:   fakeQuery,
+			MetricsEngine:      metricsMock,
 		})
 		eventProducer.lastUpdate = tt.giveLastUpdate
 		eventProducer.time = &FakeTime{time: tt.giveFakeTime}
 		err := eventProducer.Run()
 
-		if tt.wantReturnedError {
-			assert.NotNil(t, err, tt.description)
-		} else {
-			assert.Nil(t, err, tt.description)
-		}
+		assert.NotNil(t, err, tt.description)
 		assert.Equal(t, tt.wantLastUpdate, eventProducer.lastUpdate, tt.description)
 
 		var saves events.Save
@@ -411,9 +434,11 @@ func TestFetchDeltaErrors(t *testing.T) {
 		case <-time.After(10 * time.Millisecond):
 		}
 
-		assert.Equal(t, tt.wantSavedReqs, saves.Requests, tt.description)
-		assert.Equal(t, tt.wantSavedImps, saves.Imps, tt.description)
-		assert.Equal(t, tt.wantInvalidatedReqs, invalidations.Requests, tt.description)
-		assert.Equal(t, tt.wantInvalidatedImps, invalidations.Imps, tt.description)
+		assert.Nil(t, saves.Requests, tt.description)
+		assert.Nil(t, saves.Imps, tt.description)
+		assert.Nil(t, invalidations.Requests, tt.description)
+		assert.Nil(t, invalidations.Requests, tt.description)
+
+		metricsMock.AssertExpectations(t)
 	}
 }