Repository: prebid/prebid-server
Pull request #1545 (2020-10-16 02:11:36)

Add http api for fetching accounts
This works the same way as the API for stored requests,
preserving the ability to query multiple ids, so the
same API endpoint can be extended to support accounts.




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..5985afae 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 {
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/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)
 	}
 }